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