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