]> CyberLeo.Net >> Repos - FreeBSD/releng/10.3.git/blob - contrib/subversion/subversion/libsvn_client/patch.c
- Copy stable/10@296371 to releng/10.3 in preparation for 10.3-RC1
[FreeBSD/releng/10.3.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 static void
2061 svn_sort__array(apr_array_header_t *array,
2062                 int (*comparison_func)(const void *,
2063                                        const void *))
2064 {
2065   qsort(array->elts, array->nelts, array->elt_size, comparison_func);
2066 }
2067
2068 /* Implements the callback for svn_sort__array.  Puts hunks that match
2069    before hunks that do not match, puts hunks that match in order
2070    based on postion matched, puts hunks that do not match in order
2071    based on original position. */
2072 static int
2073 sort_matched_hunks(const void *a, const void *b)
2074 {
2075   const hunk_info_t *item1 = *((const hunk_info_t * const *)a);
2076   const hunk_info_t *item2 = *((const hunk_info_t * const *)b);
2077   svn_boolean_t matched1 = !item1->rejected && !item1->already_applied;
2078   svn_boolean_t matched2 = !item2->rejected && !item2->already_applied;
2079   svn_linenum_t original1, original2;
2080
2081   if (matched1 && matched2)
2082     {
2083       /* Both match so use order matched in file. */
2084       if (item1->matched_line > item2->matched_line)
2085         return 1;
2086       else if (item1->matched_line == item2->matched_line)
2087         return 0;
2088       else
2089         return -1;
2090     }
2091   else if (matched2)
2092     /* Only second matches, put it before first. */
2093     return 1;
2094   else if (matched1)
2095     /* Only first matches, put it before second. */
2096     return -1;
2097
2098   /* Neither matches, sort by original_start. */
2099   original1 = svn_diff_hunk_get_original_start(item1->hunk);
2100   original2 = svn_diff_hunk_get_original_start(item2->hunk);
2101   if (original1 > original2)
2102     return 1;
2103   else if (original1 == original2)
2104     return 0;
2105   else
2106     return -1;
2107 }
2108
2109
2110 /* Apply a PATCH to a working copy at ABS_WC_PATH and put the result
2111  * into temporary files, to be installed in the working copy later.
2112  * Return information about the patch target in *PATCH_TARGET, allocated
2113  * in RESULT_POOL. Use WC_CTX as the working copy context.
2114  * STRIP_COUNT specifies the number of leading path components
2115  * which should be stripped from target paths in the patch.
2116  * REMOVE_TEMPFILES, PATCH_FUNC, and PATCH_BATON as in svn_client_patch().
2117  * IGNORE_WHITESPACE tells whether whitespace should be considered when
2118  * doing the matching.
2119  * Call cancel CANCEL_FUNC with baton CANCEL_BATON to trigger cancellation.
2120  * Do temporary allocations in SCRATCH_POOL. */
2121 static svn_error_t *
2122 apply_one_patch(patch_target_t **patch_target, svn_patch_t *patch,
2123                 const char *abs_wc_path, svn_wc_context_t *wc_ctx,
2124                 int strip_count,
2125                 svn_boolean_t ignore_whitespace,
2126                 svn_boolean_t remove_tempfiles,
2127                 svn_client_patch_func_t patch_func,
2128                 void *patch_baton,
2129                 svn_cancel_func_t cancel_func,
2130                 void *cancel_baton,
2131                 apr_pool_t *result_pool, apr_pool_t *scratch_pool)
2132 {
2133   patch_target_t *target;
2134   apr_pool_t *iterpool;
2135   int i;
2136   static const svn_linenum_t MAX_FUZZ = 2;
2137   apr_hash_index_t *hash_index;
2138
2139   SVN_ERR(init_patch_target(&target, patch, abs_wc_path, wc_ctx, strip_count,
2140                             remove_tempfiles, result_pool, scratch_pool));
2141   if (target->skipped)
2142     {
2143       *patch_target = target;
2144       return SVN_NO_ERROR;
2145     }
2146
2147   if (patch_func)
2148     {
2149       SVN_ERR(patch_func(patch_baton, &target->filtered,
2150                          target->canon_path_from_patchfile,
2151                          target->patched_path, target->reject_path,
2152                          scratch_pool));
2153       if (target->filtered)
2154         {
2155           *patch_target = target;
2156           return SVN_NO_ERROR;
2157         }
2158     }
2159
2160   iterpool = svn_pool_create(scratch_pool);
2161   /* Match hunks. */
2162   for (i = 0; i < patch->hunks->nelts; i++)
2163     {
2164       svn_diff_hunk_t *hunk;
2165       hunk_info_t *hi;
2166       svn_linenum_t fuzz = 0;
2167
2168       svn_pool_clear(iterpool);
2169
2170       if (cancel_func)
2171         SVN_ERR(cancel_func(cancel_baton));
2172
2173       hunk = APR_ARRAY_IDX(patch->hunks, i, svn_diff_hunk_t *);
2174
2175       /* Determine the line the hunk should be applied at.
2176        * If no match is found initially, try with fuzz. */
2177       do
2178         {
2179           SVN_ERR(get_hunk_info(&hi, target, target->content, hunk, fuzz,
2180                                 ignore_whitespace,
2181                                 FALSE /* is_prop_hunk */,
2182                                 cancel_func, cancel_baton,
2183                                 result_pool, iterpool));
2184           fuzz++;
2185         }
2186       while (hi->rejected && fuzz <= MAX_FUZZ && ! hi->already_applied);
2187
2188       APR_ARRAY_PUSH(target->content->hunks, hunk_info_t *) = hi;
2189     }
2190
2191   /* Hunks are applied in the order determined by the matched line and
2192      this may be different from the order of the original lines. */
2193   svn_sort__array(target->content->hunks, sort_matched_hunks);
2194
2195   /* Apply or reject hunks. */
2196   for (i = 0; i < target->content->hunks->nelts; i++)
2197     {
2198       hunk_info_t *hi;
2199
2200       svn_pool_clear(iterpool);
2201
2202       if (cancel_func)
2203         SVN_ERR(cancel_func(cancel_baton));
2204
2205       hi = APR_ARRAY_IDX(target->content->hunks, i, hunk_info_t *);
2206       if (hi->already_applied)
2207         continue;
2208       else if (hi->rejected)
2209         SVN_ERR(reject_hunk(target, target->content, hi->hunk,
2210                             NULL /* prop_name */,
2211                             iterpool));
2212       else
2213         SVN_ERR(apply_hunk(target, target->content, hi,
2214                            NULL /* prop_name */,  iterpool));
2215     }
2216
2217   if (target->kind_on_disk == svn_node_file)
2218     {
2219       /* Copy any remaining lines to target. */
2220       SVN_ERR(copy_lines_to_target(target->content, 0, scratch_pool));
2221       if (! target->content->eof)
2222         {
2223           /* We could not copy the entire target file to the temporary file,
2224            * and would truncate the target if we copied the temporary file
2225            * on top of it. Skip this target. */
2226           target->skipped = TRUE;
2227         }
2228     }
2229
2230   /* Match property hunks. */
2231   for (hash_index = apr_hash_first(scratch_pool, patch->prop_patches);
2232        hash_index;
2233        hash_index = apr_hash_next(hash_index))
2234     {
2235       svn_prop_patch_t *prop_patch;
2236       const char *prop_name;
2237       prop_patch_target_t *prop_target;
2238
2239       prop_name = svn__apr_hash_index_key(hash_index);
2240       prop_patch = svn__apr_hash_index_val(hash_index);
2241
2242       if (! strcmp(prop_name, SVN_PROP_SPECIAL))
2243         target->is_special = TRUE;
2244
2245       /* We'll store matched hunks in prop_content. */
2246       prop_target = svn_hash_gets(target->prop_targets, prop_name);
2247
2248       for (i = 0; i < prop_patch->hunks->nelts; i++)
2249         {
2250           svn_diff_hunk_t *hunk;
2251           hunk_info_t *hi;
2252           svn_linenum_t fuzz = 0;
2253
2254           svn_pool_clear(iterpool);
2255
2256           if (cancel_func)
2257             SVN_ERR(cancel_func(cancel_baton));
2258
2259           hunk = APR_ARRAY_IDX(prop_patch->hunks, i, svn_diff_hunk_t *);
2260
2261           /* Determine the line the hunk should be applied at.
2262            * If no match is found initially, try with fuzz. */
2263           do
2264             {
2265               SVN_ERR(get_hunk_info(&hi, target, prop_target->content,
2266                                     hunk, fuzz,
2267                                     ignore_whitespace,
2268                                     TRUE /* is_prop_hunk */,
2269                                     cancel_func, cancel_baton,
2270                                     result_pool, iterpool));
2271               fuzz++;
2272             }
2273           while (hi->rejected && fuzz <= MAX_FUZZ && ! hi->already_applied);
2274
2275           APR_ARRAY_PUSH(prop_target->content->hunks, hunk_info_t *) = hi;
2276         }
2277     }
2278
2279   /* Apply or reject property hunks. */
2280   for (hash_index = apr_hash_first(scratch_pool, target->prop_targets);
2281        hash_index;
2282        hash_index = apr_hash_next(hash_index))
2283     {
2284       prop_patch_target_t *prop_target;
2285
2286       prop_target = svn__apr_hash_index_val(hash_index);
2287
2288       for (i = 0; i < prop_target->content->hunks->nelts; i++)
2289         {
2290           hunk_info_t *hi;
2291
2292           svn_pool_clear(iterpool);
2293
2294           hi = APR_ARRAY_IDX(prop_target->content->hunks, i,
2295                              hunk_info_t *);
2296           if (hi->already_applied)
2297             continue;
2298           else if (hi->rejected)
2299             SVN_ERR(reject_hunk(target, prop_target->content, hi->hunk,
2300                                 prop_target->name,
2301                                 iterpool));
2302           else
2303             SVN_ERR(apply_hunk(target, prop_target->content, hi,
2304                                prop_target->name,
2305                                iterpool));
2306         }
2307
2308         if (prop_target->content->existed)
2309           {
2310             /* Copy any remaining lines to target. */
2311             SVN_ERR(copy_lines_to_target(prop_target->content, 0,
2312                                          scratch_pool));
2313             if (! prop_target->content->eof)
2314               {
2315                 /* We could not copy the entire target property to the
2316                  * temporary file, and would truncate the target if we
2317                  * copied the temporary file on top of it. Skip this target.  */
2318                 target->skipped = TRUE;
2319               }
2320           }
2321       }
2322
2323   svn_pool_destroy(iterpool);
2324
2325   if (!target->is_symlink)
2326     {
2327       /* Now close files we don't need any longer to get their contents
2328        * flushed to disk.
2329        * But we're not closing the reject file -- it still needed and
2330        * will be closed later in write_out_rejected_hunks(). */
2331       if (target->kind_on_disk == svn_node_file)
2332         SVN_ERR(svn_io_file_close(target->file, scratch_pool));
2333
2334       SVN_ERR(svn_io_file_close(target->patched_file, scratch_pool));
2335     }
2336
2337   if (! target->skipped)
2338     {
2339       apr_finfo_t working_file;
2340       apr_finfo_t patched_file;
2341
2342       /* Get sizes of the patched temporary file and the working file.
2343        * We'll need those to figure out whether we should delete the
2344        * patched file. */
2345       SVN_ERR(svn_io_stat(&patched_file, target->patched_path,
2346                           APR_FINFO_SIZE | APR_FINFO_LINK, scratch_pool));
2347       if (target->kind_on_disk == svn_node_file)
2348         SVN_ERR(svn_io_stat(&working_file, target->local_abspath,
2349                             APR_FINFO_SIZE | APR_FINFO_LINK, scratch_pool));
2350       else
2351         working_file.size = 0;
2352
2353       if (patched_file.size == 0 && working_file.size > 0)
2354         {
2355           /* If a unidiff removes all lines from a file, that usually
2356            * means deletion, so we can confidently schedule the target
2357            * for deletion. In the rare case where the unidiff was really
2358            * meant to replace a file with an empty one, this may not
2359            * be desirable. But the deletion can easily be reverted and
2360            * creating an empty file manually is not exactly hard either. */
2361           target->deleted = (target->db_kind == svn_node_file);
2362         }
2363       else if (patched_file.size == 0 && working_file.size == 0)
2364         {
2365           /* The target was empty or non-existent to begin with
2366            * and no content was changed by patching.
2367            * Report this as skipped if it didn't exist, unless in the special
2368            * case of adding an empty file which has properties set on it or
2369            * adding an empty file with a 'git diff' */
2370           if (target->kind_on_disk == svn_node_none
2371               && ! target->has_prop_changes
2372               && ! target->added)
2373             target->skipped = TRUE;
2374         }
2375       else if (patched_file.size > 0 && working_file.size == 0)
2376         {
2377           /* The patch has created a file. */
2378           if (target->locally_deleted)
2379             target->replaced = TRUE;
2380           else if (target->db_kind == svn_node_none)
2381             target->added = TRUE;
2382         }
2383     }
2384
2385   *patch_target = target;
2386
2387   return SVN_NO_ERROR;
2388 }
2389
2390 /* Try to create missing parent directories for TARGET in the working copy
2391  * rooted at ABS_WC_PATH, and add the parents to version control.
2392  * If the parents cannot be created, mark the target as skipped.
2393  * Use client context CTX. If DRY_RUN is true, do not create missing
2394  * parents but issue notifications only.
2395  * Use SCRATCH_POOL for temporary allocations. */
2396 static svn_error_t *
2397 create_missing_parents(patch_target_t *target,
2398                        const char *abs_wc_path,
2399                        svn_client_ctx_t *ctx,
2400                        svn_boolean_t dry_run,
2401                        apr_pool_t *scratch_pool)
2402 {
2403   const char *local_abspath;
2404   apr_array_header_t *components;
2405   int present_components;
2406   int i;
2407   apr_pool_t *iterpool;
2408
2409   /* Check if we can safely create the target's parent. */
2410   local_abspath = abs_wc_path;
2411   components = svn_path_decompose(target->local_relpath, scratch_pool);
2412   present_components = 0;
2413   iterpool = svn_pool_create(scratch_pool);
2414   for (i = 0; i < components->nelts - 1; i++)
2415     {
2416       const char *component;
2417       svn_node_kind_t wc_kind, disk_kind;
2418
2419       svn_pool_clear(iterpool);
2420
2421       component = APR_ARRAY_IDX(components, i, const char *);
2422       local_abspath = svn_dirent_join(local_abspath, component, scratch_pool);
2423
2424       SVN_ERR(svn_wc_read_kind2(&wc_kind, ctx->wc_ctx, local_abspath,
2425                                 FALSE, TRUE, iterpool));
2426
2427       SVN_ERR(svn_io_check_path(local_abspath, &disk_kind, iterpool));
2428
2429       if (disk_kind == svn_node_file || wc_kind == svn_node_file)
2430         {
2431           /* on-disk files and missing files are obstructions */
2432           target->skipped = TRUE;
2433           break;
2434         }
2435       else if (disk_kind == svn_node_dir)
2436         {
2437           if (wc_kind == svn_node_dir)
2438             present_components++;
2439           else
2440             {
2441               target->skipped = TRUE;
2442               break;
2443             }
2444         }
2445       else if (wc_kind != svn_node_none)
2446         {
2447           /* Node is missing */
2448           target->skipped = TRUE;
2449           break;
2450         }
2451       else
2452         {
2453           /* It's not a file, it's not a dir...
2454              Let's add a dir */
2455           break;
2456         }
2457     }
2458   if (! target->skipped)
2459     {
2460       local_abspath = abs_wc_path;
2461       for (i = 0; i < present_components; i++)
2462         {
2463           const char *component;
2464           component = APR_ARRAY_IDX(components, i, const char *);
2465           local_abspath = svn_dirent_join(local_abspath,
2466                                           component, scratch_pool);
2467         }
2468
2469       if (!dry_run && present_components < components->nelts - 1)
2470         SVN_ERR(svn_io_make_dir_recursively(
2471                         svn_dirent_join(
2472                                    abs_wc_path,
2473                                    svn_relpath_dirname(target->local_relpath,
2474                                                        scratch_pool),
2475                                    scratch_pool),
2476                         scratch_pool));
2477
2478       for (i = present_components; i < components->nelts - 1; i++)
2479         {
2480           const char *component;
2481
2482           svn_pool_clear(iterpool);
2483
2484           component = APR_ARRAY_IDX(components, i, const char *);
2485           local_abspath = svn_dirent_join(local_abspath, component,
2486                                           scratch_pool);
2487           if (dry_run)
2488             {
2489               if (ctx->notify_func2)
2490                 {
2491                   /* Just do notification. */
2492                   svn_wc_notify_t *notify;
2493                   notify = svn_wc_create_notify(local_abspath,
2494                                                 svn_wc_notify_add,
2495                                                 iterpool);
2496                   notify->kind = svn_node_dir;
2497                   ctx->notify_func2(ctx->notify_baton2, notify,
2498                                     iterpool);
2499                 }
2500             }
2501           else
2502             {
2503               /* Create the missing component and add it
2504                * to version control. Allow cancellation since we
2505                * have not modified the working copy yet for this
2506                * target. */
2507
2508               if (ctx->cancel_func)
2509                 SVN_ERR(ctx->cancel_func(ctx->cancel_baton));
2510
2511               SVN_ERR(svn_wc_add_from_disk2(ctx->wc_ctx, local_abspath,
2512                                             NULL /*props*/,
2513                                             ctx->notify_func2, ctx->notify_baton2,
2514                                             iterpool));
2515             }
2516         }
2517     }
2518
2519   svn_pool_destroy(iterpool);
2520   return SVN_NO_ERROR;
2521 }
2522
2523 /* Install a patched TARGET into the working copy at ABS_WC_PATH.
2524  * Use client context CTX to retrieve WC_CTX, and possibly doing
2525  * notifications. If DRY_RUN is TRUE, don't modify the working copy.
2526  * Do temporary allocations in POOL. */
2527 static svn_error_t *
2528 install_patched_target(patch_target_t *target, const char *abs_wc_path,
2529                        svn_client_ctx_t *ctx, svn_boolean_t dry_run,
2530                        apr_pool_t *pool)
2531 {
2532   if (target->deleted)
2533     {
2534       if (! dry_run)
2535         {
2536           /* Schedule the target for deletion.  Suppress
2537            * notification, we'll do it manually in a minute
2538            * because we also need to notify during dry-run.
2539            * Also suppress cancellation, because we'd rather
2540            * notify about what we did before aborting. */
2541           SVN_ERR(svn_wc_delete4(ctx->wc_ctx, target->local_abspath,
2542                                  FALSE /* keep_local */, FALSE,
2543                                  NULL, NULL, NULL, NULL, pool));
2544         }
2545     }
2546   else
2547     {
2548       svn_node_kind_t parent_db_kind;
2549       if (target->added || target->replaced)
2550         {
2551           const char *parent_abspath;
2552
2553           parent_abspath = svn_dirent_dirname(target->local_abspath,
2554                                               pool);
2555           /* If the target's parent directory does not yet exist
2556            * we need to create it before we can copy the patched
2557            * result in place. */
2558           SVN_ERR(svn_wc_read_kind2(&parent_db_kind, ctx->wc_ctx,
2559                                     parent_abspath, FALSE, FALSE, pool));
2560
2561           /* We can't add targets under nodes scheduled for delete, so add
2562              a new directory if needed. */
2563           if (parent_db_kind == svn_node_dir
2564               || parent_db_kind == svn_node_file)
2565             {
2566               if (parent_db_kind != svn_node_dir)
2567                 target->skipped = TRUE;
2568               else
2569                 {
2570                   svn_node_kind_t disk_kind;
2571
2572                   SVN_ERR(svn_io_check_path(parent_abspath, &disk_kind, pool));
2573                   if (disk_kind != svn_node_dir)
2574                     target->skipped = TRUE;
2575                 }
2576             }
2577           else
2578             SVN_ERR(create_missing_parents(target, abs_wc_path, ctx,
2579                                            dry_run, pool));
2580
2581         }
2582       else
2583         {
2584           svn_node_kind_t wc_kind;
2585
2586           /* The target should exist */
2587           SVN_ERR(svn_wc_read_kind2(&wc_kind, ctx->wc_ctx,
2588                                     target->local_abspath,
2589                                     FALSE, FALSE, pool));
2590
2591           if (target->kind_on_disk == svn_node_none
2592               || wc_kind != target->kind_on_disk)
2593             {
2594               target->skipped = TRUE;
2595             }
2596         }
2597
2598       if (! dry_run && ! target->skipped)
2599         {
2600           if (target->is_special)
2601             {
2602               svn_stream_t *stream;
2603               svn_stream_t *patched_stream;
2604
2605               SVN_ERR(svn_stream_open_readonly(&patched_stream,
2606                                                target->patched_path,
2607                                                pool, pool));
2608               SVN_ERR(svn_subst_create_specialfile(&stream,
2609                                                    target->local_abspath,
2610                                                    pool, pool));
2611               SVN_ERR(svn_stream_copy3(patched_stream, stream,
2612                                        ctx->cancel_func, ctx->cancel_baton,
2613                                        pool));
2614             }
2615           else
2616             {
2617               svn_boolean_t repair_eol;
2618
2619               /* Copy the patched file on top of the target file.
2620                * Always expand keywords in the patched file, but repair EOL
2621                * only if svn:eol-style dictates a particular style. */
2622               repair_eol = (target->content->eol_style ==
2623                               svn_subst_eol_style_fixed ||
2624                             target->content->eol_style ==
2625                               svn_subst_eol_style_native);
2626
2627               SVN_ERR(svn_subst_copy_and_translate4(
2628                         target->patched_path, target->local_abspath,
2629                         target->content->eol_str, repair_eol,
2630                         target->content->keywords,
2631                         TRUE /* expand */, FALSE /* special */,
2632                         ctx->cancel_func, ctx->cancel_baton, pool));
2633             }
2634
2635           if (target->added || target->replaced)
2636             {
2637               /* The target file didn't exist previously,
2638                * so add it to version control.
2639                * Suppress notification, we'll do that later (and also
2640                * during dry-run). Don't allow cancellation because
2641                * we'd rather notify about what we did before aborting. */
2642               SVN_ERR(svn_wc_add_from_disk2(ctx->wc_ctx, target->local_abspath,
2643                                             NULL /*props*/,
2644                                             NULL, NULL, pool));
2645             }
2646
2647           /* Restore the target's executable bit if necessary. */
2648           SVN_ERR(svn_io_set_file_executable(target->local_abspath,
2649                                              target->executable,
2650                                              FALSE, pool));
2651         }
2652     }
2653
2654   return SVN_NO_ERROR;
2655 }
2656
2657 /* Write out rejected hunks, if any, to TARGET->REJECT_PATH. If DRY_RUN is
2658  * TRUE, don't modify the working copy.
2659  * Do temporary allocations in POOL.
2660  */
2661 static svn_error_t *
2662 write_out_rejected_hunks(patch_target_t *target,
2663                          svn_boolean_t dry_run,
2664                          apr_pool_t *pool)
2665 {
2666   SVN_ERR(svn_io_file_close(target->reject_file, pool));
2667
2668   if (! dry_run && (target->had_rejects || target->had_prop_rejects))
2669     {
2670       /* Write out rejected hunks, if any. */
2671       SVN_ERR(svn_io_copy_file(target->reject_path,
2672                                apr_psprintf(pool, "%s.svnpatch.rej",
2673                                target->local_abspath),
2674                                FALSE, pool));
2675       /* ### TODO mark file as conflicted. */
2676     }
2677   return SVN_NO_ERROR;
2678 }
2679
2680 /* Install the patched properties for TARGET. Use client context CTX to
2681  * retrieve WC_CTX. If DRY_RUN is TRUE, don't modify the working copy.
2682  * Do temporary allocations in SCRATCH_POOL. */
2683 static svn_error_t *
2684 install_patched_prop_targets(patch_target_t *target,
2685                              svn_client_ctx_t *ctx, svn_boolean_t dry_run,
2686                              apr_pool_t *scratch_pool)
2687 {
2688   apr_hash_index_t *hi;
2689   apr_pool_t *iterpool;
2690
2691   iterpool = svn_pool_create(scratch_pool);
2692
2693   for (hi = apr_hash_first(scratch_pool, target->prop_targets);
2694        hi;
2695        hi = apr_hash_next(hi))
2696     {
2697       prop_patch_target_t *prop_target = svn__apr_hash_index_val(hi);
2698       const svn_string_t *prop_val;
2699       svn_error_t *err;
2700
2701       svn_pool_clear(iterpool);
2702
2703       if (ctx->cancel_func)
2704         SVN_ERR(ctx->cancel_func(ctx->cancel_baton));
2705
2706       /* For a deleted prop we only set the value to NULL. */
2707       if (prop_target->operation == svn_diff_op_deleted)
2708         {
2709           if (! dry_run)
2710             SVN_ERR(svn_wc_prop_set4(ctx->wc_ctx, target->local_abspath,
2711                                      prop_target->name, NULL, svn_depth_empty,
2712                                      TRUE /* skip_checks */,
2713                                      NULL /* changelist_filter */,
2714                                      NULL, NULL /* cancellation */,
2715                                      NULL, NULL /* notification */,
2716                                      iterpool));
2717           continue;
2718         }
2719
2720       /* If the patch target doesn't exist yet, the patch wants to add an
2721        * empty file with properties set on it. So create an empty file and
2722        * add it to version control. But if the patch was in the 'git format'
2723        * then the file has already been added.
2724        *
2725        * ### How can we tell whether the patch really wanted to create
2726        * ### an empty directory? */
2727       if (! target->has_text_changes
2728           && target->kind_on_disk == svn_node_none
2729           && ! target->added)
2730         {
2731           if (! dry_run)
2732             {
2733               SVN_ERR(svn_io_file_create(target->local_abspath, "",
2734                                          scratch_pool));
2735               SVN_ERR(svn_wc_add_from_disk2(ctx->wc_ctx, target->local_abspath,
2736                                             NULL /*props*/,
2737                                             /* suppress notification */
2738                                             NULL, NULL,
2739                                             iterpool));
2740             }
2741           target->added = TRUE;
2742         }
2743
2744       /* Attempt to set the property, and reject all hunks if this
2745          fails.  If the property had a non-empty value, but now has
2746          an empty one, we'll just delete the property altogether.  */
2747       if (prop_target->value && prop_target->value->len
2748           && prop_target->patched_value && !prop_target->patched_value->len)
2749         prop_val = NULL;
2750       else
2751         prop_val = svn_stringbuf__morph_into_string(prop_target->patched_value);
2752
2753       if (dry_run)
2754         {
2755           const svn_string_t *canon_propval;
2756
2757           err = svn_wc_canonicalize_svn_prop(&canon_propval,
2758                                              prop_target->name,
2759                                              prop_val, target->local_abspath,
2760                                              target->db_kind,
2761                                              TRUE, /* ### Skipping checks */
2762                                              NULL, NULL,
2763                                              iterpool);
2764         }
2765       else
2766         {
2767           err = svn_wc_prop_set4(ctx->wc_ctx, target->local_abspath,
2768                                  prop_target->name, prop_val, svn_depth_empty,
2769                                  TRUE /* skip_checks */,
2770                                  NULL /* changelist_filter */,
2771                                  NULL, NULL /* cancellation */,
2772                                  NULL, NULL /* notification */,
2773                                  iterpool);
2774         }
2775
2776       if (err)
2777         {
2778           /* ### The errors which svn_wc_canonicalize_svn_prop() will
2779            * ### return aren't documented. */
2780           if (err->apr_err == SVN_ERR_ILLEGAL_TARGET ||
2781               err->apr_err == SVN_ERR_NODE_UNEXPECTED_KIND ||
2782               err->apr_err == SVN_ERR_IO_UNKNOWN_EOL ||
2783               err->apr_err == SVN_ERR_BAD_MIME_TYPE ||
2784               err->apr_err == SVN_ERR_CLIENT_INVALID_EXTERNALS_DESCRIPTION)
2785             {
2786               int i;
2787
2788               svn_error_clear(err);
2789
2790               for (i = 0; i < prop_target->content->hunks->nelts; i++)
2791                 {
2792                   hunk_info_t *hunk_info;
2793
2794                   hunk_info = APR_ARRAY_IDX(prop_target->content->hunks,
2795                                             i, hunk_info_t *);
2796                   hunk_info->rejected = TRUE;
2797                   SVN_ERR(reject_hunk(target, prop_target->content,
2798                                       hunk_info->hunk, prop_target->name,
2799                                       iterpool));
2800                 }
2801             }
2802           else
2803             return svn_error_trace(err);
2804         }
2805
2806     }
2807
2808   svn_pool_destroy(iterpool);
2809
2810   return SVN_NO_ERROR;
2811 }
2812
2813 /* Baton for can_delete_callback */
2814 struct can_delete_baton_t
2815 {
2816   svn_boolean_t must_keep;
2817   const apr_array_header_t *targets_info;
2818   const char *local_abspath;
2819 };
2820
2821 /* Implements svn_wc_status_func4_t. */
2822 static svn_error_t *
2823 can_delete_callback(void *baton,
2824                     const char *abspath,
2825                     const svn_wc_status3_t *status,
2826                     apr_pool_t *pool)
2827 {
2828   struct can_delete_baton_t *cb = baton;
2829   int i;
2830
2831   switch(status->node_status)
2832     {
2833       case svn_wc_status_none:
2834       case svn_wc_status_deleted:
2835         return SVN_NO_ERROR;
2836
2837       default:
2838         if (! strcmp(cb->local_abspath, abspath))
2839           return SVN_NO_ERROR; /* Only interested in descendants */
2840
2841         for (i = 0; i < cb->targets_info->nelts; i++)
2842           {
2843             const patch_target_info_t *target_info =
2844                APR_ARRAY_IDX(cb->targets_info, i, const patch_target_info_t *);
2845
2846             if (! strcmp(target_info->local_abspath, abspath))
2847               {
2848                 if (target_info->deleted)
2849                   return SVN_NO_ERROR;
2850
2851                 break; /* Cease invocation; must keep */
2852               }
2853           }
2854
2855         cb->must_keep = TRUE;
2856
2857         return svn_error_create(SVN_ERR_CEASE_INVOCATION, NULL, NULL);
2858     }
2859 }
2860
2861 static svn_error_t *
2862 check_ancestor_delete(const char *deleted_target,
2863                       apr_array_header_t *targets_info,
2864                       const char *apply_root,
2865                       svn_boolean_t dry_run,
2866                       svn_client_ctx_t *ctx,
2867                       apr_pool_t *result_pool,
2868                       apr_pool_t *scratch_pool)
2869 {
2870   struct can_delete_baton_t cb;
2871   svn_error_t *err;
2872   apr_pool_t *iterpool = svn_pool_create(scratch_pool);
2873
2874   const char *dir_abspath = svn_dirent_dirname(deleted_target, scratch_pool);
2875
2876   while (svn_dirent_is_child(apply_root, dir_abspath, iterpool))
2877     {
2878       svn_pool_clear(iterpool);
2879
2880       cb.local_abspath = dir_abspath;
2881       cb.must_keep = FALSE;
2882       cb.targets_info = targets_info;
2883
2884       err = svn_wc_walk_status(ctx->wc_ctx, dir_abspath, svn_depth_infinity,
2885                                TRUE, FALSE, FALSE, NULL,
2886                                can_delete_callback, &cb,
2887                                ctx->cancel_func, ctx->cancel_baton,
2888                                iterpool);
2889
2890       if (err)
2891         {
2892           if (err->apr_err != SVN_ERR_CEASE_INVOCATION)
2893             return svn_error_trace(err);
2894
2895           svn_error_clear(err);
2896         }
2897
2898       if (cb.must_keep)
2899       {
2900         break;
2901       }
2902
2903       if (! dry_run)
2904         {
2905           SVN_ERR(svn_wc_delete4(ctx->wc_ctx, dir_abspath, FALSE, FALSE,
2906                                  ctx->cancel_func, ctx->cancel_baton,
2907                                  NULL, NULL,
2908                                  scratch_pool));
2909         }
2910
2911       {
2912         patch_target_info_t *pti = apr_pcalloc(result_pool, sizeof(*pti));
2913
2914         pti->local_abspath = apr_pstrdup(result_pool, dir_abspath);
2915         pti->deleted = TRUE;
2916
2917         APR_ARRAY_PUSH(targets_info, patch_target_info_t *) = pti;
2918       }
2919
2920
2921       if (ctx->notify_func2)
2922         {
2923           svn_wc_notify_t *notify;
2924
2925           notify = svn_wc_create_notify(dir_abspath, svn_wc_notify_delete,
2926                                     iterpool);
2927           notify->kind = svn_node_dir;
2928
2929           ctx->notify_func2(ctx->notify_baton2, notify, iterpool);
2930         }
2931
2932       /* And check if we must also delete the parent */
2933       dir_abspath = svn_dirent_dirname(dir_abspath, scratch_pool);
2934     }
2935
2936   svn_pool_destroy(iterpool);
2937
2938   return SVN_NO_ERROR;
2939 }
2940
2941 /* This function is the main entry point into the patch code. */
2942 static svn_error_t *
2943 apply_patches(/* The path to the patch file. */
2944               const char *patch_abspath,
2945               /* The abspath to the working copy the patch should be applied to. */
2946               const char *abs_wc_path,
2947               /* Indicates whether we're doing a dry run. */
2948               svn_boolean_t dry_run,
2949               /* Number of leading components to strip from patch target paths. */
2950               int strip_count,
2951               /* Whether to apply the patch in reverse. */
2952               svn_boolean_t reverse,
2953               /* Whether to ignore whitespace when matching context lines. */
2954               svn_boolean_t ignore_whitespace,
2955               /* As in svn_client_patch(). */
2956               svn_boolean_t remove_tempfiles,
2957               /* As in svn_client_patch(). */
2958               svn_client_patch_func_t patch_func,
2959               void *patch_baton,
2960               /* The client context. */
2961               svn_client_ctx_t *ctx,
2962               apr_pool_t *scratch_pool)
2963 {
2964   svn_patch_t *patch;
2965   apr_pool_t *iterpool;
2966   svn_patch_file_t *patch_file;
2967   apr_array_header_t *targets_info;
2968
2969   /* Try to open the patch file. */
2970   SVN_ERR(svn_diff_open_patch_file(&patch_file, patch_abspath, scratch_pool));
2971
2972   /* Apply patches. */
2973   targets_info = apr_array_make(scratch_pool, 0,
2974                                 sizeof(patch_target_info_t *));
2975   iterpool = svn_pool_create(scratch_pool);
2976   do
2977     {
2978       svn_pool_clear(iterpool);
2979
2980       if (ctx->cancel_func)
2981         SVN_ERR(ctx->cancel_func(ctx->cancel_baton));
2982
2983       SVN_ERR(svn_diff_parse_next_patch(&patch, patch_file,
2984                                         reverse, ignore_whitespace,
2985                                         iterpool, iterpool));
2986       if (patch)
2987         {
2988           patch_target_t *target;
2989
2990           SVN_ERR(apply_one_patch(&target, patch, abs_wc_path,
2991                                   ctx->wc_ctx, strip_count,
2992                                   ignore_whitespace, remove_tempfiles,
2993                                   patch_func, patch_baton,
2994                                   ctx->cancel_func, ctx->cancel_baton,
2995                                   iterpool, iterpool));
2996           if (! target->filtered)
2997             {
2998               /* Save info we'll still need when we're done patching. */
2999               patch_target_info_t *target_info =
3000                 apr_pcalloc(scratch_pool, sizeof(patch_target_info_t));
3001               target_info->local_abspath = apr_pstrdup(scratch_pool,
3002                                                        target->local_abspath);
3003               target_info->deleted = target->deleted;
3004
3005               if (! target->skipped)
3006                 {
3007                   APR_ARRAY_PUSH(targets_info,
3008                                  patch_target_info_t *) = target_info;
3009
3010                   if (target->has_text_changes
3011                       || target->added
3012                       || target->deleted)
3013                     SVN_ERR(install_patched_target(target, abs_wc_path,
3014                                                    ctx, dry_run, iterpool));
3015
3016                   if (target->has_prop_changes && (!target->deleted))
3017                     SVN_ERR(install_patched_prop_targets(target, ctx,
3018                                                          dry_run, iterpool));
3019
3020                   SVN_ERR(write_out_rejected_hunks(target, dry_run, iterpool));
3021                 }
3022               SVN_ERR(send_patch_notification(target, ctx, iterpool));
3023
3024               if (target->deleted && !target->skipped)
3025                 {
3026                   SVN_ERR(check_ancestor_delete(target_info->local_abspath,
3027                                                 targets_info, abs_wc_path,
3028                                                 dry_run, ctx,
3029                                                 scratch_pool, iterpool));
3030                 }
3031             }
3032         }
3033     }
3034   while (patch);
3035
3036   SVN_ERR(svn_diff_close_patch_file(patch_file, iterpool));
3037   svn_pool_destroy(iterpool);
3038
3039   return SVN_NO_ERROR;
3040 }
3041
3042 svn_error_t *
3043 svn_client_patch(const char *patch_abspath,
3044                  const char *wc_dir_abspath,
3045                  svn_boolean_t dry_run,
3046                  int strip_count,
3047                  svn_boolean_t reverse,
3048                  svn_boolean_t ignore_whitespace,
3049                  svn_boolean_t remove_tempfiles,
3050                  svn_client_patch_func_t patch_func,
3051                  void *patch_baton,
3052                  svn_client_ctx_t *ctx,
3053                  apr_pool_t *scratch_pool)
3054 {
3055   svn_node_kind_t kind;
3056
3057   if (strip_count < 0)
3058     return svn_error_create(SVN_ERR_INCORRECT_PARAMS, NULL,
3059                             _("strip count must be positive"));
3060
3061   if (svn_path_is_url(wc_dir_abspath))
3062     return svn_error_createf(SVN_ERR_ILLEGAL_TARGET, NULL,
3063                              _("'%s' is not a local path"),
3064                              svn_dirent_local_style(wc_dir_abspath,
3065                                                     scratch_pool));
3066
3067   SVN_ERR(svn_io_check_path(patch_abspath, &kind, scratch_pool));
3068   if (kind == svn_node_none)
3069     return svn_error_createf(SVN_ERR_ILLEGAL_TARGET, NULL,
3070                              _("'%s' does not exist"),
3071                              svn_dirent_local_style(patch_abspath,
3072                                                     scratch_pool));
3073   if (kind != svn_node_file)
3074     return svn_error_createf(SVN_ERR_ILLEGAL_TARGET, NULL,
3075                              _("'%s' is not a file"),
3076                              svn_dirent_local_style(patch_abspath,
3077                                                     scratch_pool));
3078
3079   SVN_ERR(svn_io_check_path(wc_dir_abspath, &kind, scratch_pool));
3080   if (kind == svn_node_none)
3081     return svn_error_createf(SVN_ERR_ILLEGAL_TARGET, NULL,
3082                              _("'%s' does not exist"),
3083                              svn_dirent_local_style(wc_dir_abspath,
3084                                                     scratch_pool));
3085   if (kind != svn_node_dir)
3086     return svn_error_createf(SVN_ERR_ILLEGAL_TARGET, NULL,
3087                              _("'%s' is not a directory"),
3088                              svn_dirent_local_style(wc_dir_abspath,
3089                                                     scratch_pool));
3090
3091   SVN_WC__CALL_WITH_WRITE_LOCK(
3092     apply_patches(patch_abspath, wc_dir_abspath, dry_run, strip_count,
3093                   reverse, ignore_whitespace, remove_tempfiles,
3094                   patch_func, patch_baton, ctx, scratch_pool),
3095     ctx->wc_ctx, wc_dir_abspath, FALSE /* lock_anchor */, scratch_pool);
3096   return SVN_NO_ERROR;
3097 }