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