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