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