1251881Speter/*
2251881Speter * patch.c: patch application support
3251881Speter *
4251881Speter * ====================================================================
5251881Speter *    Licensed to the Apache Software Foundation (ASF) under one
6251881Speter *    or more contributor license agreements.  See the NOTICE file
7251881Speter *    distributed with this work for additional information
8251881Speter *    regarding copyright ownership.  The ASF licenses this file
9251881Speter *    to you under the Apache License, Version 2.0 (the
10251881Speter *    "License"); you may not use this file except in compliance
11251881Speter *    with the License.  You may obtain a copy of the License at
12251881Speter *
13251881Speter *      http://www.apache.org/licenses/LICENSE-2.0
14251881Speter *
15251881Speter *    Unless required by applicable law or agreed to in writing,
16251881Speter *    software distributed under the License is distributed on an
17251881Speter *    "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
18251881Speter *    KIND, either express or implied.  See the License for the
19251881Speter *    specific language governing permissions and limitations
20251881Speter *    under the License.
21251881Speter * ====================================================================
22251881Speter */
23251881Speter
24251881Speter/* ==================================================================== */
25251881Speter
26251881Speter
27251881Speter
28251881Speter/*** Includes. ***/
29251881Speter
30251881Speter#include <apr_hash.h>
31251881Speter#include <apr_fnmatch.h>
32251881Speter#include "svn_client.h"
33251881Speter#include "svn_dirent_uri.h"
34251881Speter#include "svn_diff.h"
35251881Speter#include "svn_hash.h"
36251881Speter#include "svn_io.h"
37251881Speter#include "svn_path.h"
38251881Speter#include "svn_pools.h"
39251881Speter#include "svn_props.h"
40251881Speter#include "svn_sorts.h"
41251881Speter#include "svn_subst.h"
42251881Speter#include "svn_wc.h"
43251881Speter#include "client.h"
44251881Speter
45251881Speter#include "svn_private_config.h"
46251881Speter#include "private/svn_eol_private.h"
47251881Speter#include "private/svn_wc_private.h"
48251881Speter#include "private/svn_dep_compat.h"
49251881Speter#include "private/svn_string_private.h"
50251881Speter#include "private/svn_subr_private.h"
51251881Speter
52251881Spetertypedef struct hunk_info_t {
53251881Speter  /* The hunk. */
54251881Speter  svn_diff_hunk_t *hunk;
55251881Speter
56251881Speter  /* The line where the hunk matched in the target file. */
57251881Speter  svn_linenum_t matched_line;
58251881Speter
59251881Speter  /* Whether this hunk has been rejected. */
60251881Speter  svn_boolean_t rejected;
61251881Speter
62251881Speter  /* Whether this hunk has already been applied (either manually
63251881Speter   * or by an earlier run of patch). */
64251881Speter  svn_boolean_t already_applied;
65251881Speter
66251881Speter  /* The fuzz factor used when matching this hunk, i.e. how many
67251881Speter   * lines of leading and trailing context to ignore during matching. */
68251881Speter  svn_linenum_t fuzz;
69251881Speter} hunk_info_t;
70251881Speter
71251881Speter/* A struct carrying information related to the patched and unpatched
72251881Speter * content of a target, be it a property or the text of a file. */
73251881Spetertypedef struct target_content_t {
74251881Speter  /* Indicates whether unpatched content existed prior to patching. */
75251881Speter  svn_boolean_t existed;
76251881Speter
77251881Speter  /* The line last read from the unpatched content. */
78251881Speter  svn_linenum_t current_line;
79251881Speter
80251881Speter  /* The EOL-style of the unpatched content. Either 'none', 'fixed',
81251881Speter   * or 'native'. See the documentation of svn_subst_eol_style_t. */
82251881Speter  svn_subst_eol_style_t eol_style;
83251881Speter
84251881Speter  /* If the EOL_STYLE above is not 'none', this is the EOL string
85251881Speter   * corresponding to the EOL-style. Else, it is the EOL string the
86251881Speter   * last line read from the target file was using. */
87251881Speter  const char *eol_str;
88251881Speter
89251881Speter  /* An array containing apr_off_t offsets marking the beginning of
90251881Speter   * each line in the unpatched content. */
91251881Speter  apr_array_header_t *lines;
92251881Speter
93251881Speter  /* An array containing hunk_info_t structures for hunks already matched. */
94251881Speter  apr_array_header_t *hunks;
95251881Speter
96251881Speter  /* True if end-of-file was reached while reading from the unpatched
97251881Speter   * content. */
98251881Speter  svn_boolean_t eof;
99251881Speter
100251881Speter  /* The keywords of the target. They will be contracted when reading
101251881Speter   * unpatched content and expanded when writing patched content.
102251881Speter   * When patching properties this hash is always empty. */
103251881Speter  apr_hash_t *keywords;
104251881Speter
105251881Speter  /* A callback, with an associated baton, to read a line of unpatched
106251881Speter   * content. */
107251881Speter  svn_error_t *(*readline)(void *baton, svn_stringbuf_t **line,
108251881Speter                           const char **eol_str, svn_boolean_t *eof,
109251881Speter                           apr_pool_t *result_pool, apr_pool_t *scratch_pool);
110251881Speter  void *read_baton;
111251881Speter
112251881Speter  /* A callback to get the current byte offset within the unpatched
113251881Speter   * content. Uses the read baton. */
114251881Speter  svn_error_t * (*tell)(void *baton, apr_off_t *offset,
115251881Speter                        apr_pool_t *scratch_pool);
116251881Speter
117251881Speter  /* A callback to seek to an offset within the unpatched content.
118251881Speter   * Uses the read baton. */
119251881Speter  svn_error_t * (*seek)(void *baton, apr_off_t offset,
120251881Speter                        apr_pool_t *scratch_pool);
121251881Speter
122251881Speter  /* A callback to write data to the patched content, with an
123251881Speter   * associated baton. */
124251881Speter  svn_error_t * (*write)(void *baton, const char *buf, apr_size_t len,
125251881Speter                         apr_pool_t *scratch_pool);
126251881Speter  void *write_baton;
127251881Speter
128251881Speter} target_content_t;
129251881Speter
130251881Spetertypedef struct prop_patch_target_t {
131251881Speter
132251881Speter  /* The name of the property */
133251881Speter  const char *name;
134251881Speter
135251881Speter  /* The property value. This is NULL in case the property did not exist
136251881Speter   * prior to patch application (see also CONTENT->existed).
137251881Speter   * Note that the patch implementation does not support binary properties,
138251881Speter   * so this string is not expected to contain embedded NUL characters. */
139251881Speter  const svn_string_t *value;
140251881Speter
141251881Speter  /* The patched property value.
142251881Speter   * This is equivalent to the target, except that in appropriate
143251881Speter   * places it contains the modified text as it appears in the patch file. */
144251881Speter  svn_stringbuf_t *patched_value;
145251881Speter
146251881Speter  /* All information that is specific to the content of the property. */
147251881Speter  target_content_t *content;
148251881Speter
149251881Speter  /* Represents the operation performed on the property. It can be added,
150251881Speter   * deleted or modified.
151251881Speter   * ### Should we use flags instead since we're not using all enum values? */
152251881Speter  svn_diff_operation_kind_t operation;
153251881Speter
154251881Speter  /* ### Here we'll add flags telling if the prop was added, deleted,
155251881Speter   * ### had_rejects, had_local_mods prior to patching and so on. */
156251881Speter} prop_patch_target_t;
157251881Speter
158251881Spetertypedef struct patch_target_t {
159251881Speter  /* The target path as it appeared in the patch file,
160251881Speter   * but in canonicalised form. */
161251881Speter  const char *canon_path_from_patchfile;
162251881Speter
163251881Speter  /* The target path, relative to the working copy directory the
164251881Speter   * patch is being applied to. A patch strip count applies to this
165251881Speter   * and only this path. This is never NULL. */
166251881Speter  const char *local_relpath;
167251881Speter
168251881Speter  /* The absolute path of the target on the filesystem.
169251881Speter   * Any symlinks the path from the patch file may contain are resolved.
170251881Speter   * Is not always known, so it may be NULL. */
171251881Speter  const char *local_abspath;
172251881Speter
173251881Speter  /* The target file, read-only. This is NULL in case the target
174251881Speter   * file did not exist prior to patch application (see also
175251881Speter   * CONTENT->existed). */
176251881Speter  apr_file_t *file;
177251881Speter
178251881Speter  /* The target file is a symlink */
179251881Speter  svn_boolean_t is_symlink;
180251881Speter
181251881Speter  /* The patched file.
182251881Speter   * This is equivalent to the target, except that in appropriate
183251881Speter   * places it contains the modified text as it appears in the patch file.
184251881Speter   * The data in this file is written in repository-normal form.
185251881Speter   * EOL transformation and keyword contraction is performed when the
186251881Speter   * patched result is installed in the working copy. */
187251881Speter  apr_file_t *patched_file;
188251881Speter
189251881Speter  /* Path to the patched file. */
190251881Speter  const char *patched_path;
191251881Speter
192251881Speter  /* Hunks that are rejected will be written to this file. */
193251881Speter  apr_file_t *reject_file;
194251881Speter
195251881Speter  /* Path to the reject file. */
196251881Speter  const char *reject_path;
197251881Speter
198251881Speter  /* The node kind of the target as found in WC-DB prior
199251881Speter   * to patch application. */
200251881Speter  svn_node_kind_t db_kind;
201251881Speter
202251881Speter  /* The target's kind on disk prior to patch application. */
203251881Speter  svn_node_kind_t kind_on_disk;
204251881Speter
205251881Speter  /* True if the target was locally deleted prior to patching. */
206251881Speter  svn_boolean_t locally_deleted;
207251881Speter
208251881Speter  /* True if the target had to be skipped for some reason. */
209251881Speter  svn_boolean_t skipped;
210251881Speter
211251881Speter  /* True if the target has been filtered by the patch callback. */
212251881Speter  svn_boolean_t filtered;
213251881Speter
214251881Speter  /* True if at least one hunk was rejected. */
215251881Speter  svn_boolean_t had_rejects;
216251881Speter
217251881Speter  /* True if at least one property hunk was rejected. */
218251881Speter  svn_boolean_t had_prop_rejects;
219251881Speter
220251881Speter  /* True if the target file had local modifications before the
221251881Speter   * patch was applied to it. */
222251881Speter  svn_boolean_t local_mods;
223251881Speter
224251881Speter  /* True if the target was added by the patch, which means that it did
225251881Speter   * not exist on disk before patching and has content after patching. */
226251881Speter  svn_boolean_t added;
227251881Speter
228251881Speter  /* True if the target ended up being deleted by the patch. */
229251881Speter  svn_boolean_t deleted;
230251881Speter
231251881Speter  /* True if the target ended up being replaced by the patch
232251881Speter   * (i.e. a new file was added on top locally deleted node). */
233251881Speter  svn_boolean_t replaced;
234251881Speter
235251881Speter  /* True if the target has the executable bit set. */
236251881Speter  svn_boolean_t executable;
237251881Speter
238251881Speter  /* True if the patch changed the text of the target. */
239251881Speter  svn_boolean_t has_text_changes;
240251881Speter
241251881Speter  /* True if the patch changed any of the properties of the target. */
242251881Speter  svn_boolean_t has_prop_changes;
243251881Speter
244251881Speter  /* True if the patch contained a svn:special property. */
245251881Speter  svn_boolean_t is_special;
246251881Speter
247251881Speter  /* All the information that is specific to the content of the target. */
248251881Speter  target_content_t *content;
249251881Speter
250251881Speter  /* A hash table of prop_patch_target_t objects keyed by property names. */
251251881Speter  apr_hash_t *prop_targets;
252251881Speter
253251881Speter} patch_target_t;
254251881Speter
255251881Speter
256251881Speter/* A smaller struct containing a subset of patch_target_t.
257251881Speter * Carries the minimal amount of information we still need for a
258251881Speter * target after we're done patching it so we can free other resources. */
259251881Spetertypedef struct patch_target_info_t {
260251881Speter  const char *local_abspath;
261251881Speter  svn_boolean_t deleted;
262251881Speter} patch_target_info_t;
263251881Speter
264251881Speter
265251881Speter/* Strip STRIP_COUNT components from the front of PATH, returning
266251881Speter * the result in *RESULT, allocated in RESULT_POOL.
267251881Speter * Do temporary allocations in SCRATCH_POOL. */
268251881Speterstatic svn_error_t *
269251881Speterstrip_path(const char **result, const char *path, int strip_count,
270251881Speter           apr_pool_t *result_pool, apr_pool_t *scratch_pool)
271251881Speter{
272251881Speter  int i;
273251881Speter  apr_array_header_t *components;
274251881Speter  apr_array_header_t *stripped;
275251881Speter
276251881Speter  components = svn_path_decompose(path, scratch_pool);
277251881Speter  if (strip_count > components->nelts)
278251881Speter    return svn_error_createf(SVN_ERR_CLIENT_PATCH_BAD_STRIP_COUNT, NULL,
279251881Speter                             _("Cannot strip %u components from '%s'"),
280251881Speter                             strip_count,
281251881Speter                             svn_dirent_local_style(path, scratch_pool));
282251881Speter
283251881Speter  stripped = apr_array_make(scratch_pool, components->nelts - strip_count,
284251881Speter                            sizeof(const char *));
285251881Speter  for (i = strip_count; i < components->nelts; i++)
286251881Speter    {
287251881Speter      const char *component;
288251881Speter
289251881Speter      component = APR_ARRAY_IDX(components, i, const char *);
290251881Speter      APR_ARRAY_PUSH(stripped, const char *) = component;
291251881Speter    }
292251881Speter
293251881Speter  *result = svn_path_compose(stripped, result_pool);
294251881Speter
295251881Speter  return SVN_NO_ERROR;
296251881Speter}
297251881Speter
298251881Speter/* Obtain KEYWORDS, EOL_STYLE and EOL_STR for LOCAL_ABSPATH.
299251881Speter * WC_CTX is a context for the working copy the patch is applied to.
300251881Speter * Use RESULT_POOL for allocations of fields in TARGET.
301251881Speter * Use SCRATCH_POOL for all other allocations. */
302251881Speterstatic svn_error_t *
303251881Speterobtain_eol_and_keywords_for_file(apr_hash_t **keywords,
304251881Speter                                 svn_subst_eol_style_t *eol_style,
305251881Speter                                 const char **eol_str,
306251881Speter                                 svn_wc_context_t *wc_ctx,
307251881Speter                                 const char *local_abspath,
308251881Speter                                 apr_pool_t *result_pool,
309251881Speter                                 apr_pool_t *scratch_pool)
310251881Speter{
311251881Speter  apr_hash_t *props;
312251881Speter  svn_string_t *keywords_val, *eol_style_val;
313251881Speter
314251881Speter  SVN_ERR(svn_wc_prop_list2(&props, wc_ctx, local_abspath,
315251881Speter                            scratch_pool, scratch_pool));
316251881Speter  keywords_val = svn_hash_gets(props, SVN_PROP_KEYWORDS);
317251881Speter  if (keywords_val)
318251881Speter    {
319251881Speter      svn_revnum_t changed_rev;
320251881Speter      apr_time_t changed_date;
321251881Speter      const char *rev_str;
322251881Speter      const char *author;
323251881Speter      const char *url;
324251881Speter      const char *root_url;
325251881Speter
326251881Speter      SVN_ERR(svn_wc__node_get_changed_info(&changed_rev,
327251881Speter                                            &changed_date,
328251881Speter                                            &author, wc_ctx,
329251881Speter                                            local_abspath,
330251881Speter                                            scratch_pool,
331251881Speter                                            scratch_pool));
332251881Speter      rev_str = apr_psprintf(scratch_pool, "%ld", changed_rev);
333251881Speter      SVN_ERR(svn_wc__node_get_url(&url, wc_ctx,
334251881Speter                                   local_abspath,
335251881Speter                                   scratch_pool, scratch_pool));
336251881Speter      SVN_ERR(svn_wc__node_get_repos_info(NULL, NULL, &root_url, NULL,
337251881Speter                                          wc_ctx, local_abspath,
338251881Speter                                          scratch_pool, scratch_pool));
339251881Speter      SVN_ERR(svn_subst_build_keywords3(keywords,
340251881Speter                                        keywords_val->data,
341251881Speter                                        rev_str, url, root_url, changed_date,
342251881Speter                                        author, result_pool));
343251881Speter    }
344251881Speter
345251881Speter  eol_style_val = svn_hash_gets(props, SVN_PROP_EOL_STYLE);
346251881Speter  if (eol_style_val)
347251881Speter    {
348251881Speter      svn_subst_eol_style_from_value(eol_style,
349251881Speter                                     eol_str,
350251881Speter                                     eol_style_val->data);
351251881Speter    }
352251881Speter
353251881Speter  return SVN_NO_ERROR;
354251881Speter}
355251881Speter
356251881Speter/* Resolve the exact path for a patch TARGET at path PATH_FROM_PATCHFILE,
357251881Speter * which is the path of the target as it appeared in the patch file.
358251881Speter * Put a canonicalized version of PATH_FROM_PATCHFILE into
359251881Speter * TARGET->CANON_PATH_FROM_PATCHFILE.
360251881Speter * WC_CTX is a context for the working copy the patch is applied to.
361251881Speter * If possible, determine TARGET->WC_PATH, TARGET->ABS_PATH, TARGET->KIND,
362251881Speter * TARGET->ADDED, and TARGET->PARENT_DIR_EXISTS.
363251881Speter * Indicate in TARGET->SKIPPED whether the target should be skipped.
364251881Speter * STRIP_COUNT specifies the number of leading path components
365251881Speter * which should be stripped from target paths in the patch.
366251881Speter * PROP_CHANGES_ONLY specifies whether the target path is allowed to have
367251881Speter * only property changes, and no content changes (in which case the target
368251881Speter * must be a directory).
369251881Speter * Use RESULT_POOL for allocations of fields in TARGET.
370251881Speter * Use SCRATCH_POOL for all other allocations. */
371251881Speterstatic svn_error_t *
372251881Speterresolve_target_path(patch_target_t *target,
373251881Speter                    const char *path_from_patchfile,
374251881Speter                    const char *wcroot_abspath,
375251881Speter                    int strip_count,
376251881Speter                    svn_boolean_t prop_changes_only,
377251881Speter                    svn_wc_context_t *wc_ctx,
378251881Speter                    apr_pool_t *result_pool,
379251881Speter                    apr_pool_t *scratch_pool)
380251881Speter{
381251881Speter  const char *stripped_path;
382251881Speter  svn_wc_status3_t *status;
383251881Speter  svn_error_t *err;
384251881Speter  svn_boolean_t under_root;
385251881Speter
386251881Speter  target->canon_path_from_patchfile = svn_dirent_internal_style(
387251881Speter                                        path_from_patchfile, result_pool);
388251881Speter
389251881Speter  /* We allow properties to be set on the wc root dir. */
390251881Speter  if (! prop_changes_only && target->canon_path_from_patchfile[0] == '\0')
391251881Speter    {
392251881Speter      /* An empty patch target path? What gives? Skip this. */
393251881Speter      target->skipped = TRUE;
394251881Speter      target->local_abspath = NULL;
395251881Speter      target->local_relpath = "";
396251881Speter      return SVN_NO_ERROR;
397251881Speter    }
398251881Speter
399251881Speter  if (strip_count > 0)
400251881Speter    SVN_ERR(strip_path(&stripped_path, target->canon_path_from_patchfile,
401251881Speter                       strip_count, result_pool, scratch_pool));
402251881Speter  else
403251881Speter    stripped_path = target->canon_path_from_patchfile;
404251881Speter
405251881Speter  if (svn_dirent_is_absolute(stripped_path))
406251881Speter    {
407251881Speter      target->local_relpath = svn_dirent_is_child(wcroot_abspath,
408251881Speter                                                  stripped_path,
409251881Speter                                                  result_pool);
410251881Speter
411251881Speter      if (! target->local_relpath)
412251881Speter        {
413251881Speter          /* The target path is either outside of the working copy
414251881Speter           * or it is the working copy itself. Skip it. */
415251881Speter          target->skipped = TRUE;
416251881Speter          target->local_abspath = NULL;
417251881Speter          target->local_relpath = stripped_path;
418251881Speter          return SVN_NO_ERROR;
419251881Speter        }
420251881Speter    }
421251881Speter  else
422251881Speter    {
423251881Speter      target->local_relpath = stripped_path;
424251881Speter    }
425251881Speter
426251881Speter  /* Make sure the path is secure to use. We want the target to be inside
427251881Speter   * of the working copy and not be fooled by symlinks it might contain. */
428251881Speter  SVN_ERR(svn_dirent_is_under_root(&under_root,
429251881Speter                                   &target->local_abspath, wcroot_abspath,
430251881Speter                                   target->local_relpath, result_pool));
431251881Speter
432251881Speter  if (! under_root)
433251881Speter    {
434251881Speter      /* The target path is outside of the working copy. Skip it. */
435251881Speter      target->skipped = TRUE;
436251881Speter      target->local_abspath = NULL;
437251881Speter      return SVN_NO_ERROR;
438251881Speter    }
439251881Speter
440251881Speter  /* Skip things we should not be messing with. */
441251881Speter  err = svn_wc_status3(&status, wc_ctx, target->local_abspath,
442251881Speter                       result_pool, scratch_pool);
443251881Speter  if (err)
444251881Speter    {
445251881Speter      if (err->apr_err != SVN_ERR_WC_PATH_NOT_FOUND)
446251881Speter        return svn_error_trace(err);
447251881Speter
448251881Speter      svn_error_clear(err);
449251881Speter
450251881Speter      target->locally_deleted = TRUE;
451251881Speter      target->db_kind = svn_node_none;
452251881Speter      status = NULL;
453251881Speter    }
454251881Speter  else if (status->node_status == svn_wc_status_ignored ||
455251881Speter           status->node_status == svn_wc_status_unversioned ||
456251881Speter           status->node_status == svn_wc_status_missing ||
457251881Speter           status->node_status == svn_wc_status_obstructed ||
458251881Speter           status->conflicted)
459251881Speter    {
460251881Speter      target->skipped = TRUE;
461251881Speter      return SVN_NO_ERROR;
462251881Speter    }
463251881Speter  else if (status->node_status == svn_wc_status_deleted)
464251881Speter    {
465251881Speter      target->locally_deleted = TRUE;
466251881Speter    }
467251881Speter
468251881Speter  if (status && (status->kind != svn_node_unknown))
469251881Speter    target->db_kind = status->kind;
470251881Speter  else
471251881Speter    target->db_kind = svn_node_none;
472251881Speter
473251881Speter  SVN_ERR(svn_io_check_special_path(target->local_abspath,
474251881Speter                                    &target->kind_on_disk, &target->is_symlink,
475251881Speter                                    scratch_pool));
476251881Speter
477251881Speter  if (target->locally_deleted)
478251881Speter    {
479251881Speter      const char *moved_to_abspath;
480251881Speter
481251881Speter      SVN_ERR(svn_wc__node_was_moved_away(&moved_to_abspath, NULL,
482251881Speter                                          wc_ctx, target->local_abspath,
483251881Speter                                          result_pool, scratch_pool));
484251881Speter      if (moved_to_abspath)
485251881Speter        {
486251881Speter          target->local_abspath = moved_to_abspath;
487251881Speter          target->local_relpath = svn_dirent_skip_ancestor(wcroot_abspath,
488251881Speter                                                          moved_to_abspath);
489251881Speter          SVN_ERR_ASSERT(target->local_relpath &&
490251881Speter                         target->local_relpath[0] != '\0');
491251881Speter
492251881Speter          /* As far as we are concerned this target is not locally deleted. */
493251881Speter          target->locally_deleted = FALSE;
494251881Speter
495251881Speter          SVN_ERR(svn_io_check_special_path(target->local_abspath,
496251881Speter                                            &target->kind_on_disk,
497251881Speter                                            &target->is_symlink,
498251881Speter                                            scratch_pool));
499251881Speter        }
500251881Speter      else if (target->kind_on_disk != svn_node_none)
501251881Speter        {
502251881Speter          target->skipped = TRUE;
503251881Speter          return SVN_NO_ERROR;
504251881Speter        }
505251881Speter    }
506251881Speter
507251881Speter  return SVN_NO_ERROR;
508251881Speter}
509251881Speter
510251881Speter/* Baton for reading from properties. */
511251881Spetertypedef struct prop_read_baton_t {
512251881Speter  const svn_string_t *value;
513251881Speter  apr_off_t offset;
514251881Speter} prop_read_baton_t;
515251881Speter
516251881Speter/* Allocate *STRINGBUF in RESULT_POOL, and read into it one line from
517251881Speter * the unpatched property value accessed via BATON.
518251881Speter * Reading stops either after a line-terminator was found, or if
519251881Speter * the property value runs out in which case *EOF is set to TRUE.
520251881Speter * The line-terminator is not stored in *STRINGBUF.
521251881Speter *
522251881Speter * If the line is empty or could not be read, *line is set to NULL.
523251881Speter *
524251881Speter * The line-terminator is detected automatically and stored in *EOL
525251881Speter * if EOL is not NULL. If the end of the property value is reached
526251881Speter * and does not end with a newline character, and EOL is not NULL,
527251881Speter * *EOL is set to NULL.
528251881Speter *
529251881Speter * SCRATCH_POOL is used for temporary allocations.
530251881Speter */
531251881Speterstatic svn_error_t *
532251881Speterreadline_prop(void *baton, svn_stringbuf_t **line, const char **eol_str,
533251881Speter              svn_boolean_t *eof, apr_pool_t *result_pool,
534251881Speter              apr_pool_t *scratch_pool)
535251881Speter{
536251881Speter  prop_read_baton_t *b = (prop_read_baton_t *)baton;
537251881Speter  svn_stringbuf_t *str = NULL;
538251881Speter  const char *c;
539251881Speter  svn_boolean_t found_eof;
540251881Speter
541251881Speter  if ((apr_uint64_t)b->offset >= (apr_uint64_t)b->value->len)
542251881Speter    {
543251881Speter      *eol_str = NULL;
544251881Speter      *eof = TRUE;
545251881Speter      *line = NULL;
546251881Speter      return SVN_NO_ERROR;
547251881Speter    }
548251881Speter
549251881Speter  /* Read bytes into STR up to and including, but not storing,
550251881Speter   * the next EOL sequence. */
551251881Speter  *eol_str = NULL;
552251881Speter  found_eof = FALSE;
553251881Speter  do
554251881Speter    {
555251881Speter      c = b->value->data + b->offset;
556251881Speter      b->offset++;
557251881Speter
558251881Speter      if (*c == '\0')
559251881Speter        {
560251881Speter          found_eof = TRUE;
561251881Speter          break;
562251881Speter        }
563251881Speter      else if (*c == '\n')
564251881Speter        {
565251881Speter          *eol_str = "\n";
566251881Speter        }
567251881Speter      else if (*c == '\r')
568251881Speter        {
569251881Speter          *eol_str = "\r";
570251881Speter          if (*(c + 1) == '\n')
571251881Speter            {
572251881Speter              *eol_str = "\r\n";
573251881Speter              b->offset++;
574251881Speter            }
575251881Speter        }
576251881Speter      else
577251881Speter        {
578251881Speter          if (str == NULL)
579251881Speter            str = svn_stringbuf_create_ensure(80, result_pool);
580251881Speter          svn_stringbuf_appendbyte(str, *c);
581251881Speter        }
582251881Speter
583251881Speter      if (*eol_str)
584251881Speter        break;
585251881Speter    }
586251881Speter  while (c < b->value->data + b->value->len);
587251881Speter
588251881Speter  if (eof)
589251881Speter    *eof = found_eof;
590251881Speter  *line = str;
591251881Speter
592251881Speter  return SVN_NO_ERROR;
593251881Speter}
594251881Speter
595251881Speter/* Return in *OFFSET the current byte offset for reading from the
596251881Speter * unpatched property value accessed via BATON.
597251881Speter * Use SCRATCH_POOL for temporary allocations. */
598251881Speterstatic svn_error_t *
599251881Spetertell_prop(void *baton, apr_off_t *offset, apr_pool_t *scratch_pool)
600251881Speter{
601251881Speter  prop_read_baton_t *b = (prop_read_baton_t *)baton;
602251881Speter  *offset = b->offset;
603251881Speter  return SVN_NO_ERROR;
604251881Speter}
605251881Speter
606251881Speter/* Seek to the specified by OFFSET in the unpatched property value accessed
607251881Speter * via BATON. Use SCRATCH_POOL for temporary allocations. */
608251881Speterstatic svn_error_t *
609251881Speterseek_prop(void *baton, apr_off_t offset, apr_pool_t *scratch_pool)
610251881Speter{
611251881Speter  prop_read_baton_t *b = (prop_read_baton_t *)baton;
612251881Speter  b->offset = offset;
613251881Speter  return SVN_NO_ERROR;
614251881Speter}
615251881Speter
616251881Speter/* Write LEN bytes from BUF into the patched property value accessed
617251881Speter * via BATON. Use SCRATCH_POOL for temporary allocations. */
618251881Speterstatic svn_error_t *
619251881Speterwrite_prop(void *baton, const char *buf, apr_size_t len,
620251881Speter           apr_pool_t *scratch_pool)
621251881Speter{
622251881Speter  svn_stringbuf_t *patched_value = (svn_stringbuf_t *)baton;
623251881Speter  svn_stringbuf_appendbytes(patched_value, buf, len);
624251881Speter  return SVN_NO_ERROR;
625251881Speter}
626251881Speter
627251881Speter/* Initialize a PROP_TARGET structure for PROP_NAME on the patch target
628251881Speter * at LOCAL_ABSPATH. OPERATION indicates the operation performed on the
629251881Speter * property. Use working copy context WC_CTX.
630251881Speter * Allocate results in RESULT_POOL.
631251881Speter * Use SCRATCH_POOL for temporary allocations. */
632251881Speterstatic svn_error_t *
633251881Speterinit_prop_target(prop_patch_target_t **prop_target,
634251881Speter                 const char *prop_name,
635251881Speter                 svn_diff_operation_kind_t operation,
636251881Speter                 svn_wc_context_t *wc_ctx,
637251881Speter                 const char *local_abspath,
638251881Speter                 apr_pool_t *result_pool, apr_pool_t *scratch_pool)
639251881Speter{
640251881Speter  prop_patch_target_t *new_prop_target;
641251881Speter  target_content_t *content;
642251881Speter  const svn_string_t *value;
643251881Speter  svn_error_t *err;
644251881Speter  prop_read_baton_t *prop_read_baton;
645251881Speter
646251881Speter  content = apr_pcalloc(result_pool, sizeof(*content));
647251881Speter
648251881Speter  /* All other fields are FALSE or NULL due to apr_pcalloc(). */
649251881Speter  content->current_line = 1;
650251881Speter  content->eol_style = svn_subst_eol_style_none;
651251881Speter  content->lines = apr_array_make(result_pool, 0, sizeof(apr_off_t));
652251881Speter  content->hunks = apr_array_make(result_pool, 0, sizeof(hunk_info_t *));
653251881Speter  content->keywords = apr_hash_make(result_pool);
654251881Speter
655251881Speter  new_prop_target = apr_pcalloc(result_pool, sizeof(*new_prop_target));
656251881Speter  new_prop_target->name = apr_pstrdup(result_pool, prop_name);
657251881Speter  new_prop_target->operation = operation;
658251881Speter  new_prop_target->content = content;
659251881Speter
660251881Speter  err = svn_wc_prop_get2(&value, wc_ctx, local_abspath, prop_name,
661251881Speter                         result_pool, scratch_pool);
662251881Speter  if (err)
663251881Speter    {
664251881Speter      if (err->apr_err == SVN_ERR_WC_PATH_NOT_FOUND)
665251881Speter        {
666251881Speter          svn_error_clear(err);
667251881Speter          value = NULL;
668251881Speter        }
669251881Speter      else
670251881Speter        return svn_error_trace(err);
671251881Speter    }
672251881Speter  content->existed = (value != NULL);
673251881Speter  new_prop_target->value = value;
674251881Speter  new_prop_target->patched_value = svn_stringbuf_create_empty(result_pool);
675251881Speter
676251881Speter
677251881Speter  /* Wire up the read and write callbacks. */
678251881Speter  prop_read_baton = apr_pcalloc(result_pool, sizeof(*prop_read_baton));
679251881Speter  prop_read_baton->value = value;
680251881Speter  prop_read_baton->offset = 0;
681251881Speter  content->readline = readline_prop;
682251881Speter  content->tell = tell_prop;
683251881Speter  content->seek = seek_prop;
684251881Speter  content->read_baton = prop_read_baton;
685251881Speter  content->write = write_prop;
686251881Speter  content->write_baton = new_prop_target->patched_value;
687251881Speter
688251881Speter  *prop_target = new_prop_target;
689251881Speter
690251881Speter  return SVN_NO_ERROR;
691251881Speter}
692251881Speter
693251881Speter/* Allocate *STRINGBUF in RESULT_POOL, and read into it one line from
694251881Speter * the unpatched file content accessed via BATON.
695251881Speter * Reading stops either after a line-terminator was found,
696251881Speter * or if EOF is reached in which case *EOF is set to TRUE.
697251881Speter * The line-terminator is not stored in *STRINGBUF.
698251881Speter *
699251881Speter * If the line is empty or could not be read, *line is set to NULL.
700251881Speter *
701251881Speter * The line-terminator is detected automatically and stored in *EOL
702251881Speter * if EOL is not NULL. If EOF is reached and FILE does not end
703251881Speter * with a newline character, and EOL is not NULL, *EOL is set to NULL.
704251881Speter *
705251881Speter * SCRATCH_POOL is used for temporary allocations.
706251881Speter */
707251881Speterstatic svn_error_t *
708251881Speterreadline_file(void *baton, svn_stringbuf_t **line, const char **eol_str,
709251881Speter              svn_boolean_t *eof, apr_pool_t *result_pool,
710251881Speter              apr_pool_t *scratch_pool)
711251881Speter{
712251881Speter  apr_file_t *file = (apr_file_t *)baton;
713251881Speter  svn_stringbuf_t *str = NULL;
714251881Speter  apr_size_t numbytes;
715251881Speter  char c;
716251881Speter  svn_boolean_t found_eof;
717251881Speter
718251881Speter  /* Read bytes into STR up to and including, but not storing,
719251881Speter   * the next EOL sequence. */
720251881Speter  *eol_str = NULL;
721251881Speter  numbytes = 1;
722251881Speter  found_eof = FALSE;
723251881Speter  while (!found_eof)
724251881Speter    {
725251881Speter      SVN_ERR(svn_io_file_read_full2(file, &c, sizeof(c), &numbytes,
726251881Speter                                     &found_eof, scratch_pool));
727251881Speter      if (numbytes != 1)
728251881Speter        {
729251881Speter          found_eof = TRUE;
730251881Speter          break;
731251881Speter        }
732251881Speter
733251881Speter      if (c == '\n')
734251881Speter        {
735251881Speter          *eol_str = "\n";
736251881Speter        }
737251881Speter      else if (c == '\r')
738251881Speter        {
739251881Speter          *eol_str = "\r";
740251881Speter
741251881Speter          if (!found_eof)
742251881Speter            {
743251881Speter              apr_off_t pos;
744251881Speter
745251881Speter              /* Check for "\r\n" by peeking at the next byte. */
746251881Speter              pos = 0;
747251881Speter              SVN_ERR(svn_io_file_seek(file, APR_CUR, &pos, scratch_pool));
748251881Speter              SVN_ERR(svn_io_file_read_full2(file, &c, sizeof(c), &numbytes,
749251881Speter                                             &found_eof, scratch_pool));
750251881Speter              if (numbytes == 1 && c == '\n')
751251881Speter                {
752251881Speter                  *eol_str = "\r\n";
753251881Speter                }
754251881Speter              else
755251881Speter                {
756251881Speter                  /* Pretend we never peeked. */
757251881Speter                  SVN_ERR(svn_io_file_seek(file, APR_SET, &pos, scratch_pool));
758251881Speter                  found_eof = FALSE;
759251881Speter                  numbytes = 1;
760251881Speter                }
761251881Speter            }
762251881Speter        }
763251881Speter      else
764251881Speter        {
765251881Speter          if (str == NULL)
766251881Speter            str = svn_stringbuf_create_ensure(80, result_pool);
767251881Speter          svn_stringbuf_appendbyte(str, c);
768251881Speter        }
769251881Speter
770251881Speter      if (*eol_str)
771251881Speter        break;
772251881Speter    }
773251881Speter
774251881Speter  if (eof)
775251881Speter    *eof = found_eof;
776251881Speter  *line = str;
777251881Speter
778251881Speter  return SVN_NO_ERROR;
779251881Speter}
780251881Speter
781251881Speter/* Return in *OFFSET the current byte offset for reading from the
782251881Speter * unpatched file content accessed via BATON.
783251881Speter * Use SCRATCH_POOL for temporary allocations. */
784251881Speterstatic svn_error_t *
785251881Spetertell_file(void *baton, apr_off_t *offset, apr_pool_t *scratch_pool)
786251881Speter{
787251881Speter  apr_file_t *file = (apr_file_t *)baton;
788251881Speter  *offset = 0;
789251881Speter  SVN_ERR(svn_io_file_seek(file, APR_CUR, offset, scratch_pool));
790251881Speter  return SVN_NO_ERROR;
791251881Speter}
792251881Speter
793251881Speter/* Seek to the specified by OFFSET in the unpatched file content accessed
794251881Speter * via BATON. Use SCRATCH_POOL for temporary allocations. */
795251881Speterstatic svn_error_t *
796251881Speterseek_file(void *baton, apr_off_t offset, apr_pool_t *scratch_pool)
797251881Speter{
798251881Speter  apr_file_t *file = (apr_file_t *)baton;
799251881Speter  SVN_ERR(svn_io_file_seek(file, APR_SET, &offset, scratch_pool));
800251881Speter  return SVN_NO_ERROR;
801251881Speter}
802251881Speter
803251881Speter/* Write LEN bytes from BUF into the patched file content accessed
804251881Speter * via BATON. Use SCRATCH_POOL for temporary allocations. */
805251881Speterstatic svn_error_t *
806251881Speterwrite_file(void *baton, const char *buf, apr_size_t len,
807251881Speter           apr_pool_t *scratch_pool)
808251881Speter{
809251881Speter  apr_file_t *file = (apr_file_t *)baton;
810251881Speter  SVN_ERR(svn_io_file_write_full(file, buf, len, &len, scratch_pool));
811251881Speter  return SVN_NO_ERROR;
812251881Speter}
813251881Speter
814251881Speter/* Handling symbolic links:
815251881Speter *
816251881Speter * In Subversion, symlinks can be represented on disk in two distinct ways.
817251881Speter * On systems which support symlinks, a symlink is created on disk.
818251881Speter * On systems which do not support symlink, a file is created on disk
819251881Speter * which contains the "normal form" of the symlink, which looks like:
820251881Speter *   link TARGET
821251881Speter * where TARGET is the file the symlink points to.
822251881Speter *
823251881Speter * When reading symlinks (i.e. the link itself, not the file the symlink
824251881Speter * is pointing to) through the svn_subst_create_specialfile() function
825251881Speter * into a buffer, the buffer always contains the "normal form" of the symlink.
826251881Speter * Due to this representation symlinks always contain a single line of text.
827251881Speter *
828251881Speter * The functions below are needed to deal with the case where a patch
829251881Speter * wants to change the TARGET that a symlink points to.
830251881Speter */
831251881Speter
832251881Speter/* Baton for the (readline|tell|seek|write)_symlink functions. */
833251881Speterstruct symlink_baton_t
834251881Speter{
835251881Speter  /* The path to the symlink on disk (not the path to the target of the link) */
836251881Speter  const char *local_abspath;
837251881Speter
838251881Speter  /* Indicates whether the "normal form" of the symlink has been read. */
839251881Speter  svn_boolean_t at_eof;
840251881Speter};
841251881Speter
842251881Speter/* Allocate *STRINGBUF in RESULT_POOL, and store into it the "normal form"
843251881Speter * of the symlink accessed via BATON.
844251881Speter *
845251881Speter * Otherwise behaves like readline_file(), which see.
846251881Speter */
847251881Speterstatic svn_error_t *
848251881Speterreadline_symlink(void *baton, svn_stringbuf_t **line, const char **eol_str,
849251881Speter                 svn_boolean_t *eof, apr_pool_t *result_pool,
850251881Speter                 apr_pool_t *scratch_pool)
851251881Speter{
852251881Speter  struct symlink_baton_t *sb = baton;
853251881Speter
854251881Speter  if (eof)
855251881Speter    *eof = TRUE;
856251881Speter  if (eol_str)
857251881Speter    *eol_str = NULL;
858251881Speter
859251881Speter  if (sb->at_eof)
860251881Speter    {
861251881Speter      *line = NULL;
862251881Speter    }
863251881Speter  else
864251881Speter    {
865251881Speter      svn_string_t *dest;
866251881Speter
867251881Speter      SVN_ERR(svn_io_read_link(&dest, sb->local_abspath, scratch_pool));
868251881Speter      *line = svn_stringbuf_createf(result_pool, "link %s", dest->data);
869251881Speter      sb->at_eof = TRUE;
870251881Speter    }
871251881Speter
872251881Speter  return SVN_NO_ERROR;
873251881Speter}
874251881Speter
875251881Speter/* Set *OFFSET to 1 or 0 depending on whether the "normal form" of
876251881Speter * the symlink has already been read. */
877251881Speterstatic svn_error_t *
878251881Spetertell_symlink(void *baton, apr_off_t *offset, apr_pool_t *scratch_pool)
879251881Speter{
880251881Speter  struct symlink_baton_t *sb = baton;
881251881Speter
882251881Speter  *offset = sb->at_eof ? 1 : 0;
883251881Speter  return SVN_NO_ERROR;
884251881Speter}
885251881Speter
886251881Speter/* If offset is non-zero, mark the symlink as having been read in its
887251881Speter * "normal form". Else, mark the symlink as not having been read yet. */
888251881Speterstatic svn_error_t *
889251881Speterseek_symlink(void *baton, apr_off_t offset, apr_pool_t *scratch_pool)
890251881Speter{
891251881Speter  struct symlink_baton_t *sb = baton;
892251881Speter
893251881Speter  sb->at_eof = (offset != 0);
894251881Speter  return SVN_NO_ERROR;
895251881Speter}
896251881Speter
897251881Speter
898251881Speter/* Set the target of the symlink accessed via BATON.
899251881Speter * The contents of BUF must be a valid "normal form" of a symlink. */
900251881Speterstatic svn_error_t *
901251881Speterwrite_symlink(void *baton, const char *buf, apr_size_t len,
902251881Speter              apr_pool_t *scratch_pool)
903251881Speter{
904251881Speter  const char *target_abspath = baton;
905251881Speter  const char *new_name;
906251881Speter  const char *link = apr_pstrndup(scratch_pool, buf, len);
907251881Speter
908251881Speter  if (strncmp(link, "link ", 5) != 0)
909251881Speter    return svn_error_create(SVN_ERR_IO_WRITE_ERROR, NULL,
910251881Speter                            _("Invalid link representation"));
911251881Speter
912251881Speter  link += 5; /* Skip "link " */
913251881Speter
914251881Speter  /* We assume the entire symlink is written at once, as the patch
915251881Speter     format is line based */
916251881Speter
917251881Speter  SVN_ERR(svn_io_create_unique_link(&new_name, target_abspath, link,
918251881Speter                                    ".tmp", scratch_pool));
919251881Speter
920251881Speter  SVN_ERR(svn_io_file_rename(new_name, target_abspath, scratch_pool));
921251881Speter
922251881Speter  return SVN_NO_ERROR;
923251881Speter}
924251881Speter
925251881Speter
926251881Speter/* Return a suitable filename for the target of PATCH.
927251881Speter * Examine the ``old'' and ``new'' file names, and choose the file name
928251881Speter * with the fewest path components, the shortest basename, and the shortest
929251881Speter * total file name length (in that order). In case of a tie, return the new
930251881Speter * filename. This heuristic is also used by Larry Wall's UNIX patch (except
931251881Speter * that it prompts for a filename in case of a tie).
932251881Speter * Additionally, for compatibility with git, if one of the filenames
933251881Speter * is "/dev/null", use the other filename. */
934251881Speterstatic const char *
935251881Speterchoose_target_filename(const svn_patch_t *patch)
936251881Speter{
937251881Speter  apr_size_t old;
938251881Speter  apr_size_t new;
939251881Speter
940251881Speter  if (strcmp(patch->old_filename, "/dev/null") == 0)
941251881Speter    return patch->new_filename;
942251881Speter  if (strcmp(patch->new_filename, "/dev/null") == 0)
943251881Speter    return patch->old_filename;
944251881Speter
945251881Speter  old = svn_path_component_count(patch->old_filename);
946251881Speter  new = svn_path_component_count(patch->new_filename);
947251881Speter
948251881Speter  if (old == new)
949251881Speter    {
950251881Speter      old = strlen(svn_dirent_basename(patch->old_filename, NULL));
951251881Speter      new = strlen(svn_dirent_basename(patch->new_filename, NULL));
952251881Speter
953251881Speter      if (old == new)
954251881Speter        {
955251881Speter          old = strlen(patch->old_filename);
956251881Speter          new = strlen(patch->new_filename);
957251881Speter        }
958251881Speter    }
959251881Speter
960251881Speter  return (old < new) ? patch->old_filename : patch->new_filename;
961251881Speter}
962251881Speter
963251881Speter/* Attempt to initialize a *PATCH_TARGET structure for a target file
964251881Speter * described by PATCH. Use working copy context WC_CTX.
965251881Speter * STRIP_COUNT specifies the number of leading path components
966251881Speter * which should be stripped from target paths in the patch.
967251881Speter * The patch target structure is allocated in RESULT_POOL, but if the target
968251881Speter * should be skipped, PATCH_TARGET->SKIPPED is set and the target should be
969251881Speter * treated as not fully initialized, e.g. the caller should not not do any
970251881Speter * further operations on the target if it is marked to be skipped.
971251881Speter * If REMOVE_TEMPFILES is TRUE, set up temporary files to be removed as
972251881Speter * soon as they are no longer needed.
973251881Speter * Use SCRATCH_POOL for all other allocations. */
974251881Speterstatic svn_error_t *
975251881Speterinit_patch_target(patch_target_t **patch_target,
976251881Speter                  const svn_patch_t *patch,
977251881Speter                  const char *wcroot_abspath,
978251881Speter                  svn_wc_context_t *wc_ctx, int strip_count,
979251881Speter                  svn_boolean_t remove_tempfiles,
980251881Speter                  apr_pool_t *result_pool, apr_pool_t *scratch_pool)
981251881Speter{
982251881Speter  patch_target_t *target;
983251881Speter  target_content_t *content;
984251881Speter  svn_boolean_t has_prop_changes = FALSE;
985251881Speter  svn_boolean_t prop_changes_only = FALSE;
986251881Speter
987251881Speter  {
988251881Speter    apr_hash_index_t *hi;
989251881Speter
990251881Speter    for (hi = apr_hash_first(scratch_pool, patch->prop_patches);
991251881Speter         hi;
992251881Speter         hi = apr_hash_next(hi))
993251881Speter      {
994251881Speter        svn_prop_patch_t *prop_patch = svn__apr_hash_index_val(hi);
995251881Speter        if (! has_prop_changes)
996251881Speter          has_prop_changes = prop_patch->hunks->nelts > 0;
997251881Speter        else
998251881Speter          break;
999251881Speter      }
1000251881Speter  }
1001251881Speter
1002251881Speter  prop_changes_only = has_prop_changes && patch->hunks->nelts == 0;
1003251881Speter
1004251881Speter  content = apr_pcalloc(result_pool, sizeof(*content));
1005251881Speter
1006251881Speter  /* All other fields in content are FALSE or NULL due to apr_pcalloc().*/
1007251881Speter  content->current_line = 1;
1008251881Speter  content->eol_style = svn_subst_eol_style_none;
1009251881Speter  content->lines = apr_array_make(result_pool, 0, sizeof(apr_off_t));
1010251881Speter  content->hunks = apr_array_make(result_pool, 0, sizeof(hunk_info_t *));
1011251881Speter  content->keywords = apr_hash_make(result_pool);
1012251881Speter
1013251881Speter  target = apr_pcalloc(result_pool, sizeof(*target));
1014251881Speter
1015251881Speter  /* All other fields in target are FALSE or NULL due to apr_pcalloc(). */
1016251881Speter  target->db_kind = svn_node_none;
1017251881Speter  target->kind_on_disk = svn_node_none;
1018251881Speter  target->content = content;
1019251881Speter  target->prop_targets = apr_hash_make(result_pool);
1020251881Speter
1021251881Speter  SVN_ERR(resolve_target_path(target, choose_target_filename(patch),
1022251881Speter                              wcroot_abspath, strip_count, prop_changes_only,
1023251881Speter                              wc_ctx, result_pool, scratch_pool));
1024251881Speter  if (! target->skipped)
1025251881Speter    {
1026251881Speter      const char *diff_header;
1027251881Speter      apr_size_t len;
1028251881Speter
1029251881Speter      /* Create a temporary file to write the patched result to.
1030251881Speter       * Also grab various bits of information about the file. */
1031251881Speter      if (target->is_symlink)
1032251881Speter        {
1033251881Speter          struct symlink_baton_t *sb = apr_pcalloc(result_pool, sizeof(*sb));
1034251881Speter          content->existed = TRUE;
1035251881Speter
1036251881Speter          sb->local_abspath = target->local_abspath;
1037251881Speter
1038251881Speter          /* Wire up the read callbacks. */
1039251881Speter          content->read_baton = sb;
1040251881Speter
1041251881Speter          content->readline = readline_symlink;
1042251881Speter          content->seek = seek_symlink;
1043251881Speter          content->tell = tell_symlink;
1044251881Speter        }
1045251881Speter      else if (target->kind_on_disk == svn_node_file)
1046251881Speter        {
1047251881Speter          SVN_ERR(svn_io_file_open(&target->file, target->local_abspath,
1048251881Speter                                   APR_READ | APR_BUFFERED,
1049251881Speter                                   APR_OS_DEFAULT, result_pool));
1050251881Speter          SVN_ERR(svn_wc_text_modified_p2(&target->local_mods, wc_ctx,
1051251881Speter                                          target->local_abspath, FALSE,
1052251881Speter                                          scratch_pool));
1053251881Speter          SVN_ERR(svn_io_is_file_executable(&target->executable,
1054251881Speter                                            target->local_abspath,
1055251881Speter                                            scratch_pool));
1056251881Speter          SVN_ERR(obtain_eol_and_keywords_for_file(&content->keywords,
1057251881Speter                                                   &content->eol_style,
1058251881Speter                                                   &content->eol_str,
1059251881Speter                                                   wc_ctx,
1060251881Speter                                                   target->local_abspath,
1061251881Speter                                                   result_pool,
1062251881Speter                                                   scratch_pool));
1063251881Speter          content->existed = TRUE;
1064251881Speter
1065251881Speter          /* Wire up the read callbacks. */
1066251881Speter          content->readline = readline_file;
1067251881Speter          content->seek = seek_file;
1068251881Speter          content->tell = tell_file;
1069251881Speter          content->read_baton = target->file;
1070251881Speter        }
1071251881Speter
1072251881Speter      /* ### Is it ok to set the operation of the target already here? Isn't
1073251881Speter       * ### the target supposed to be marked with an operation after we have
1074251881Speter       * ### determined that the changes will apply cleanly to the WC? Maybe
1075251881Speter       * ### we should have kept the patch field in patch_target_t to be
1076251881Speter       * ### able to distinguish between 'what the patch says we should do'
1077251881Speter       * ### and 'what we can do with the given state of our WC'. */
1078251881Speter      if (patch->operation == svn_diff_op_added)
1079251881Speter        target->added = TRUE;
1080251881Speter      else if (patch->operation == svn_diff_op_deleted)
1081251881Speter        target->deleted = TRUE;
1082251881Speter
1083251881Speter      if (! target->is_symlink)
1084251881Speter        {
1085251881Speter          /* Open a temporary file to write the patched result to. */
1086251881Speter          SVN_ERR(svn_io_open_unique_file3(&target->patched_file,
1087251881Speter                                           &target->patched_path, NULL,
1088251881Speter                                           remove_tempfiles ?
1089251881Speter                                             svn_io_file_del_on_pool_cleanup :
1090251881Speter                                             svn_io_file_del_none,
1091251881Speter                                           result_pool, scratch_pool));
1092251881Speter
1093251881Speter          /* Put the write callback in place. */
1094251881Speter          content->write = write_file;
1095251881Speter          content->write_baton = target->patched_file;
1096251881Speter        }
1097251881Speter      else
1098251881Speter        {
1099251881Speter          /* Put the write callback in place. */
1100251881Speter          SVN_ERR(svn_io_open_unique_file3(NULL,
1101251881Speter                                           &target->patched_path, NULL,
1102251881Speter                                           remove_tempfiles ?
1103251881Speter                                             svn_io_file_del_on_pool_cleanup :
1104251881Speter                                             svn_io_file_del_none,
1105251881Speter                                           result_pool, scratch_pool));
1106251881Speter
1107251881Speter          content->write_baton = (void*)target->patched_path;
1108251881Speter
1109251881Speter          content->write = write_symlink;
1110251881Speter        }
1111251881Speter
1112251881Speter      /* Open a temporary file to write rejected hunks to. */
1113251881Speter      SVN_ERR(svn_io_open_unique_file3(&target->reject_file,
1114251881Speter                                       &target->reject_path, NULL,
1115251881Speter                                       remove_tempfiles ?
1116251881Speter                                         svn_io_file_del_on_pool_cleanup :
1117251881Speter                                         svn_io_file_del_none,
1118251881Speter                                       result_pool, scratch_pool));
1119251881Speter
1120251881Speter      /* The reject file needs a diff header. */
1121251881Speter      diff_header = apr_psprintf(scratch_pool, "--- %s%s+++ %s%s",
1122251881Speter                                 target->canon_path_from_patchfile,
1123251881Speter                                 APR_EOL_STR,
1124251881Speter                                 target->canon_path_from_patchfile,
1125251881Speter                                 APR_EOL_STR);
1126251881Speter      len = strlen(diff_header);
1127251881Speter      SVN_ERR(svn_io_file_write_full(target->reject_file, diff_header, len,
1128251881Speter                                     &len, scratch_pool));
1129251881Speter
1130251881Speter      /* Handle properties. */
1131251881Speter      if (! target->skipped)
1132251881Speter        {
1133251881Speter          apr_hash_index_t *hi;
1134251881Speter
1135251881Speter          for (hi = apr_hash_first(result_pool, patch->prop_patches);
1136251881Speter               hi;
1137251881Speter               hi = apr_hash_next(hi))
1138251881Speter            {
1139251881Speter              const char *prop_name = svn__apr_hash_index_key(hi);
1140251881Speter              svn_prop_patch_t *prop_patch = svn__apr_hash_index_val(hi);
1141251881Speter              prop_patch_target_t *prop_target;
1142251881Speter
1143251881Speter              SVN_ERR(init_prop_target(&prop_target,
1144251881Speter                                       prop_name,
1145251881Speter                                       prop_patch->operation,
1146251881Speter                                       wc_ctx, target->local_abspath,
1147251881Speter                                       result_pool, scratch_pool));
1148251881Speter              svn_hash_sets(target->prop_targets, prop_name, prop_target);
1149251881Speter            }
1150251881Speter        }
1151251881Speter    }
1152251881Speter
1153251881Speter  *patch_target = target;
1154251881Speter  return SVN_NO_ERROR;
1155251881Speter}
1156251881Speter
1157251881Speter/* Read a *LINE from CONTENT. If the line has not been read before
1158251881Speter * mark the line in CONTENT->LINES.
1159251881Speter * If a line could be read successfully, increase CONTENT->CURRENT_LINE,
1160251881Speter * and allocate *LINE in RESULT_POOL.
1161251881Speter * Do temporary allocations in SCRATCH_POOL.
1162251881Speter */
1163251881Speterstatic svn_error_t *
1164251881Speterreadline(target_content_t *content,
1165251881Speter         const char **line,
1166251881Speter         apr_pool_t *result_pool,
1167251881Speter         apr_pool_t *scratch_pool)
1168251881Speter{
1169251881Speter  svn_stringbuf_t *line_raw;
1170251881Speter  const char *eol_str;
1171251881Speter  svn_linenum_t max_line = (svn_linenum_t)content->lines->nelts + 1;
1172251881Speter
1173251881Speter  if (content->eof || content->readline == NULL)
1174251881Speter    {
1175251881Speter      *line = "";
1176251881Speter      return SVN_NO_ERROR;
1177251881Speter    }
1178251881Speter
1179251881Speter  SVN_ERR_ASSERT(content->current_line <= max_line);
1180251881Speter  if (content->current_line == max_line)
1181251881Speter    {
1182251881Speter      apr_off_t offset;
1183251881Speter
1184251881Speter      SVN_ERR(content->tell(content->read_baton, &offset,
1185251881Speter                            scratch_pool));
1186251881Speter      APR_ARRAY_PUSH(content->lines, apr_off_t) = offset;
1187251881Speter    }
1188251881Speter
1189251881Speter  SVN_ERR(content->readline(content->read_baton, &line_raw,
1190251881Speter                            &eol_str, &content->eof,
1191251881Speter                            result_pool, scratch_pool));
1192251881Speter  if (content->eol_style == svn_subst_eol_style_none)
1193251881Speter    content->eol_str = eol_str;
1194251881Speter
1195251881Speter  if (line_raw)
1196251881Speter    {
1197251881Speter      /* Contract keywords. */
1198251881Speter      SVN_ERR(svn_subst_translate_cstring2(line_raw->data, line,
1199251881Speter                                           NULL, FALSE,
1200251881Speter                                           content->keywords, FALSE,
1201251881Speter                                           result_pool));
1202251881Speter    }
1203251881Speter  else
1204251881Speter    *line = "";
1205251881Speter
1206251881Speter  if ((line_raw && line_raw->len > 0) || eol_str)
1207251881Speter    content->current_line++;
1208251881Speter
1209251881Speter  SVN_ERR_ASSERT(content->current_line > 0);
1210251881Speter
1211251881Speter  return SVN_NO_ERROR;
1212251881Speter}
1213251881Speter
1214251881Speter/* Seek to the specified LINE in CONTENT.
1215251881Speter * Mark any lines not read before in CONTENT->LINES.
1216251881Speter * Do temporary allocations in SCRATCH_POOL.
1217251881Speter */
1218251881Speterstatic svn_error_t *
1219251881Speterseek_to_line(target_content_t *content, svn_linenum_t line,
1220251881Speter             apr_pool_t *scratch_pool)
1221251881Speter{
1222251881Speter  svn_linenum_t saved_line;
1223251881Speter  svn_boolean_t saved_eof;
1224251881Speter
1225251881Speter  SVN_ERR_ASSERT(line > 0);
1226251881Speter
1227251881Speter  if (line == content->current_line)
1228251881Speter    return SVN_NO_ERROR;
1229251881Speter
1230251881Speter  saved_line = content->current_line;
1231251881Speter  saved_eof = content->eof;
1232251881Speter
1233251881Speter  if (line <= (svn_linenum_t)content->lines->nelts)
1234251881Speter    {
1235251881Speter      apr_off_t offset;
1236251881Speter
1237251881Speter      offset = APR_ARRAY_IDX(content->lines, line - 1, apr_off_t);
1238251881Speter      SVN_ERR(content->seek(content->read_baton, offset,
1239251881Speter                            scratch_pool));
1240251881Speter      content->current_line = line;
1241251881Speter    }
1242251881Speter  else
1243251881Speter    {
1244251881Speter      const char *dummy;
1245251881Speter      apr_pool_t *iterpool = svn_pool_create(scratch_pool);
1246251881Speter
1247251881Speter      while (! content->eof && content->current_line < line)
1248251881Speter        {
1249251881Speter          svn_pool_clear(iterpool);
1250251881Speter          SVN_ERR(readline(content, &dummy, iterpool, iterpool));
1251251881Speter        }
1252251881Speter      svn_pool_destroy(iterpool);
1253251881Speter    }
1254251881Speter
1255251881Speter  /* After seeking backwards from EOF position clear EOF indicator. */
1256251881Speter  if (saved_eof && saved_line > content->current_line)
1257251881Speter    content->eof = FALSE;
1258251881Speter
1259251881Speter  return SVN_NO_ERROR;
1260251881Speter}
1261251881Speter
1262251881Speter/* Indicate in *MATCHED whether the original text of HUNK matches the patch
1263251881Speter * CONTENT at its current line. Lines within FUZZ lines of the start or
1264251881Speter * end of HUNK will always match. If IGNORE_WHITESPACE is set, we ignore
1265251881Speter * whitespace when doing the matching. When this function returns, neither
1266251881Speter * CONTENT->CURRENT_LINE nor the file offset in the target file will
1267251881Speter * have changed. If MATCH_MODIFIED is TRUE, match the modified hunk text,
1268251881Speter * rather than the original hunk text.
1269251881Speter * Do temporary allocations in POOL. */
1270251881Speterstatic svn_error_t *
1271251881Spetermatch_hunk(svn_boolean_t *matched, target_content_t *content,
1272251881Speter           svn_diff_hunk_t *hunk, svn_linenum_t fuzz,
1273251881Speter           svn_boolean_t ignore_whitespace,
1274251881Speter           svn_boolean_t match_modified, apr_pool_t *pool)
1275251881Speter{
1276251881Speter  svn_stringbuf_t *hunk_line;
1277251881Speter  const char *target_line;
1278251881Speter  svn_linenum_t lines_read;
1279251881Speter  svn_linenum_t saved_line;
1280251881Speter  svn_boolean_t hunk_eof;
1281251881Speter  svn_boolean_t lines_matched;
1282251881Speter  apr_pool_t *iterpool;
1283251881Speter  svn_linenum_t hunk_length;
1284251881Speter  svn_linenum_t leading_context;
1285251881Speter  svn_linenum_t trailing_context;
1286251881Speter
1287251881Speter  *matched = FALSE;
1288251881Speter
1289251881Speter  if (content->eof)
1290251881Speter    return SVN_NO_ERROR;
1291251881Speter
1292251881Speter  saved_line = content->current_line;
1293251881Speter  lines_read = 0;
1294251881Speter  lines_matched = FALSE;
1295251881Speter  leading_context = svn_diff_hunk_get_leading_context(hunk);
1296251881Speter  trailing_context = svn_diff_hunk_get_trailing_context(hunk);
1297251881Speter  if (match_modified)
1298251881Speter    {
1299251881Speter      svn_diff_hunk_reset_modified_text(hunk);
1300251881Speter      hunk_length = svn_diff_hunk_get_modified_length(hunk);
1301251881Speter    }
1302251881Speter  else
1303251881Speter    {
1304251881Speter      svn_diff_hunk_reset_original_text(hunk);
1305251881Speter      hunk_length = svn_diff_hunk_get_original_length(hunk);
1306251881Speter    }
1307251881Speter  iterpool = svn_pool_create(pool);
1308251881Speter  do
1309251881Speter    {
1310251881Speter      const char *hunk_line_translated;
1311251881Speter
1312251881Speter      svn_pool_clear(iterpool);
1313251881Speter
1314251881Speter      if (match_modified)
1315251881Speter        SVN_ERR(svn_diff_hunk_readline_modified_text(hunk, &hunk_line,
1316251881Speter                                                     NULL, &hunk_eof,
1317251881Speter                                                     iterpool, iterpool));
1318251881Speter      else
1319251881Speter        SVN_ERR(svn_diff_hunk_readline_original_text(hunk, &hunk_line,
1320251881Speter                                                     NULL, &hunk_eof,
1321251881Speter                                                     iterpool, iterpool));
1322251881Speter
1323251881Speter      /* Contract keywords, if any, before matching. */
1324251881Speter      SVN_ERR(svn_subst_translate_cstring2(hunk_line->data,
1325251881Speter                                           &hunk_line_translated,
1326251881Speter                                           NULL, FALSE,
1327251881Speter                                           content->keywords, FALSE,
1328251881Speter                                           iterpool));
1329251881Speter      SVN_ERR(readline(content, &target_line, iterpool, iterpool));
1330251881Speter
1331251881Speter      lines_read++;
1332251881Speter
1333251881Speter      /* If the last line doesn't have a newline, we get EOF but still
1334251881Speter       * have a non-empty line to compare. */
1335251881Speter      if ((hunk_eof && hunk_line->len == 0) ||
1336251881Speter          (content->eof && *target_line == 0))
1337251881Speter        break;
1338251881Speter
1339251881Speter      /* Leading/trailing fuzzy lines always match. */
1340251881Speter      if ((lines_read <= fuzz && leading_context > fuzz) ||
1341251881Speter          (lines_read > hunk_length - fuzz && trailing_context > fuzz))
1342251881Speter        lines_matched = TRUE;
1343251881Speter      else
1344251881Speter        {
1345251881Speter          if (ignore_whitespace)
1346251881Speter            {
1347251881Speter              char *hunk_line_trimmed;
1348251881Speter              char *target_line_trimmed;
1349251881Speter
1350251881Speter              hunk_line_trimmed = apr_pstrdup(iterpool, hunk_line_translated);
1351251881Speter              target_line_trimmed = apr_pstrdup(iterpool, target_line);
1352251881Speter              apr_collapse_spaces(hunk_line_trimmed, hunk_line_trimmed);
1353251881Speter              apr_collapse_spaces(target_line_trimmed, target_line_trimmed);
1354251881Speter              lines_matched = ! strcmp(hunk_line_trimmed, target_line_trimmed);
1355251881Speter            }
1356251881Speter          else
1357251881Speter            lines_matched = ! strcmp(hunk_line_translated, target_line);
1358251881Speter        }
1359251881Speter    }
1360251881Speter  while (lines_matched);
1361251881Speter
1362251881Speter  *matched = lines_matched && hunk_eof && hunk_line->len == 0;
1363251881Speter  SVN_ERR(seek_to_line(content, saved_line, iterpool));
1364251881Speter  svn_pool_destroy(iterpool);
1365251881Speter
1366251881Speter  return SVN_NO_ERROR;
1367251881Speter}
1368251881Speter
1369251881Speter/* Scan lines of CONTENT for a match of the original text of HUNK,
1370251881Speter * up to but not including the specified UPPER_LINE. Use fuzz factor FUZZ.
1371251881Speter * If UPPER_LINE is zero scan until EOF occurs when reading from TARGET.
1372251881Speter * Return the line at which HUNK was matched in *MATCHED_LINE.
1373251881Speter * If the hunk did not match at all, set *MATCHED_LINE to zero.
1374251881Speter * If the hunk matched multiple times, and MATCH_FIRST is TRUE,
1375251881Speter * return the line number at which the first match occurred in *MATCHED_LINE.
1376251881Speter * If the hunk matched multiple times, and MATCH_FIRST is FALSE,
1377251881Speter * return the line number at which the last match occurred in *MATCHED_LINE.
1378251881Speter * If IGNORE_WHITESPACE is set, ignore whitespace during the matching.
1379251881Speter * If MATCH_MODIFIED is TRUE, match the modified hunk text,
1380251881Speter * rather than the original hunk text.
1381251881Speter * Call cancel CANCEL_FUNC with baton CANCEL_BATON to trigger cancellation.
1382251881Speter * Do all allocations in POOL. */
1383251881Speterstatic svn_error_t *
1384251881Speterscan_for_match(svn_linenum_t *matched_line,
1385251881Speter               target_content_t *content,
1386251881Speter               svn_diff_hunk_t *hunk, svn_boolean_t match_first,
1387251881Speter               svn_linenum_t upper_line, svn_linenum_t fuzz,
1388251881Speter               svn_boolean_t ignore_whitespace,
1389251881Speter               svn_boolean_t match_modified,
1390251881Speter               svn_cancel_func_t cancel_func, void *cancel_baton,
1391251881Speter               apr_pool_t *pool)
1392251881Speter{
1393251881Speter  apr_pool_t *iterpool;
1394251881Speter
1395251881Speter  *matched_line = 0;
1396251881Speter  iterpool = svn_pool_create(pool);
1397251881Speter  while ((content->current_line < upper_line || upper_line == 0) &&
1398251881Speter         ! content->eof)
1399251881Speter    {
1400251881Speter      svn_boolean_t matched;
1401251881Speter
1402251881Speter      svn_pool_clear(iterpool);
1403251881Speter
1404251881Speter      if (cancel_func)
1405251881Speter        SVN_ERR(cancel_func(cancel_baton));
1406251881Speter
1407251881Speter      SVN_ERR(match_hunk(&matched, content, hunk, fuzz, ignore_whitespace,
1408251881Speter                         match_modified, iterpool));
1409251881Speter      if (matched)
1410251881Speter        {
1411251881Speter          svn_boolean_t taken = FALSE;
1412251881Speter          int i;
1413251881Speter
1414251881Speter          /* Don't allow hunks to match at overlapping locations. */
1415251881Speter          for (i = 0; i < content->hunks->nelts; i++)
1416251881Speter            {
1417251881Speter              const hunk_info_t *hi;
1418251881Speter              svn_linenum_t length;
1419251881Speter
1420251881Speter              hi = APR_ARRAY_IDX(content->hunks, i, const hunk_info_t *);
1421251881Speter
1422251881Speter              if (match_modified)
1423251881Speter                length = svn_diff_hunk_get_modified_length(hi->hunk);
1424251881Speter              else
1425251881Speter                length = svn_diff_hunk_get_original_length(hi->hunk);
1426251881Speter
1427251881Speter              taken = (! hi->rejected &&
1428251881Speter                       content->current_line >= hi->matched_line &&
1429251881Speter                       content->current_line < (hi->matched_line + length));
1430251881Speter              if (taken)
1431251881Speter                break;
1432251881Speter            }
1433251881Speter
1434251881Speter          if (! taken)
1435251881Speter            {
1436251881Speter              *matched_line = content->current_line;
1437251881Speter              if (match_first)
1438251881Speter                break;
1439251881Speter            }
1440251881Speter        }
1441251881Speter
1442251881Speter      if (! content->eof)
1443251881Speter        SVN_ERR(seek_to_line(content, content->current_line + 1,
1444251881Speter                             iterpool));
1445251881Speter    }
1446251881Speter  svn_pool_destroy(iterpool);
1447251881Speter
1448251881Speter  return SVN_NO_ERROR;
1449251881Speter}
1450251881Speter
1451251881Speter/* Indicate in *MATCH whether the content described by CONTENT
1452251881Speter * matches the modified text of HUNK.
1453251881Speter * Use SCRATCH_POOL for temporary allocations. */
1454251881Speterstatic svn_error_t *
1455251881Spetermatch_existing_target(svn_boolean_t *match,
1456251881Speter                      target_content_t *content,
1457251881Speter                      svn_diff_hunk_t *hunk,
1458251881Speter                      apr_pool_t *scratch_pool)
1459251881Speter{
1460251881Speter  svn_boolean_t lines_matched;
1461251881Speter  apr_pool_t *iterpool;
1462251881Speter  svn_boolean_t hunk_eof;
1463251881Speter  svn_linenum_t saved_line;
1464251881Speter
1465251881Speter  svn_diff_hunk_reset_modified_text(hunk);
1466251881Speter
1467251881Speter  saved_line = content->current_line;
1468251881Speter
1469251881Speter  iterpool = svn_pool_create(scratch_pool);
1470251881Speter  do
1471251881Speter    {
1472251881Speter      const char *line;
1473251881Speter      svn_stringbuf_t *hunk_line;
1474251881Speter      const char *line_translated;
1475251881Speter      const char *hunk_line_translated;
1476251881Speter
1477251881Speter      svn_pool_clear(iterpool);
1478251881Speter
1479251881Speter      SVN_ERR(readline(content, &line, iterpool, iterpool));
1480251881Speter      SVN_ERR(svn_diff_hunk_readline_modified_text(hunk, &hunk_line,
1481251881Speter                                                   NULL, &hunk_eof,
1482251881Speter                                                   iterpool, iterpool));
1483251881Speter      /* Contract keywords. */
1484251881Speter      SVN_ERR(svn_subst_translate_cstring2(line, &line_translated,
1485251881Speter                                           NULL, FALSE,
1486251881Speter                                           content->keywords,
1487251881Speter                                           FALSE, iterpool));
1488251881Speter      SVN_ERR(svn_subst_translate_cstring2(hunk_line->data,
1489251881Speter                                           &hunk_line_translated,
1490251881Speter                                           NULL, FALSE,
1491251881Speter                                           content->keywords,
1492251881Speter                                           FALSE, iterpool));
1493251881Speter      lines_matched = ! strcmp(line_translated, hunk_line_translated);
1494251881Speter      if (content->eof != hunk_eof)
1495251881Speter        {
1496251881Speter          svn_pool_destroy(iterpool);
1497251881Speter          *match = FALSE;
1498251881Speter          return SVN_NO_ERROR;
1499251881Speter        }
1500251881Speter      }
1501251881Speter    while (lines_matched && ! content->eof && ! hunk_eof);
1502251881Speter    svn_pool_destroy(iterpool);
1503251881Speter
1504251881Speter    *match = (lines_matched && content->eof == hunk_eof);
1505251881Speter    SVN_ERR(seek_to_line(content, saved_line, scratch_pool));
1506251881Speter
1507251881Speter    return SVN_NO_ERROR;
1508251881Speter}
1509251881Speter
1510251881Speter/* Determine the line at which a HUNK applies to CONTENT of the TARGET
1511251881Speter * file, and return an appropriate hunk_info object in *HI, allocated from
1512251881Speter * RESULT_POOL. Use fuzz factor FUZZ. Set HI->FUZZ to FUZZ. If no correct
1513251881Speter * line can be determined, set HI->REJECTED to TRUE.
1514251881Speter * IGNORE_WHITESPACE tells whether whitespace should be considered when
1515251881Speter * matching. IS_PROP_HUNK indicates whether the hunk patches file content
1516251881Speter * or a property.
1517251881Speter * When this function returns, neither CONTENT->CURRENT_LINE nor
1518251881Speter * the file offset in the target file will have changed.
1519251881Speter * Call cancel CANCEL_FUNC with baton CANCEL_BATON to trigger cancellation.
1520251881Speter * Do temporary allocations in POOL. */
1521251881Speterstatic svn_error_t *
1522251881Speterget_hunk_info(hunk_info_t **hi, patch_target_t *target,
1523251881Speter              target_content_t *content,
1524251881Speter              svn_diff_hunk_t *hunk, svn_linenum_t fuzz,
1525251881Speter              svn_boolean_t ignore_whitespace,
1526251881Speter              svn_boolean_t is_prop_hunk,
1527251881Speter              svn_cancel_func_t cancel_func, void *cancel_baton,
1528251881Speter              apr_pool_t *result_pool, apr_pool_t *scratch_pool)
1529251881Speter{
1530251881Speter  svn_linenum_t matched_line;
1531251881Speter  svn_linenum_t original_start;
1532251881Speter  svn_boolean_t already_applied;
1533251881Speter
1534251881Speter  original_start = svn_diff_hunk_get_original_start(hunk);
1535251881Speter  already_applied = FALSE;
1536251881Speter
1537251881Speter  /* An original offset of zero means that this hunk wants to create
1538251881Speter   * a new file. Don't bother matching hunks in that case, since
1539251881Speter   * the hunk applies at line 1. If the file already exists, the hunk
1540251881Speter   * is rejected, unless the file is versioned and its content matches
1541251881Speter   * the file the patch wants to create.  */
1542251881Speter  if (original_start == 0 && fuzz > 0)
1543251881Speter    {
1544251881Speter      matched_line = 0; /* reject any fuzz for new files */
1545251881Speter    }
1546251881Speter  else if (original_start == 0 && ! is_prop_hunk)
1547251881Speter    {
1548251881Speter      if (target->kind_on_disk == svn_node_file)
1549251881Speter        {
1550251881Speter          const svn_io_dirent2_t *dirent;
1551251881Speter          SVN_ERR(svn_io_stat_dirent2(&dirent, target->local_abspath, FALSE,
1552251881Speter                                      TRUE, scratch_pool, scratch_pool));
1553251881Speter
1554251881Speter          if (dirent->kind == svn_node_file
1555251881Speter              && !dirent->special
1556251881Speter              && dirent->filesize == 0)
1557251881Speter            {
1558251881Speter              matched_line = 1; /* Matched an on-disk empty file */
1559251881Speter            }
1560251881Speter          else
1561251881Speter            {
1562251881Speter              if (target->db_kind == svn_node_file)
1563251881Speter                {
1564251881Speter                  svn_boolean_t file_matches;
1565251881Speter
1566251881Speter                  /* ### I can't reproduce anything but a no-match here.
1567251881Speter                         The content is already at eof, so any hunk fails */
1568251881Speter                  SVN_ERR(match_existing_target(&file_matches, content, hunk,
1569251881Speter                                            scratch_pool));
1570251881Speter                  if (file_matches)
1571251881Speter                    {
1572251881Speter                      matched_line = 1;
1573251881Speter                      already_applied = TRUE;
1574251881Speter                    }
1575251881Speter                  else
1576251881Speter                    matched_line = 0; /* reject */
1577251881Speter                }
1578251881Speter              else
1579251881Speter                matched_line = 0; /* reject */
1580251881Speter            }
1581251881Speter        }
1582251881Speter      else
1583251881Speter        matched_line = 1;
1584251881Speter    }
1585251881Speter  /* Same conditions apply as for the file case above.
1586251881Speter   *
1587251881Speter   * ### Since the hunk says the prop should be added we just assume so for
1588251881Speter   * ### now and don't bother with storing the previous lines and such. When
1589251881Speter   * ### we have the diff operation available we can just check for adds. */
1590251881Speter  else if (original_start == 0 && is_prop_hunk)
1591251881Speter    {
1592251881Speter      if (content->existed)
1593251881Speter        {
1594251881Speter          svn_boolean_t prop_matches;
1595251881Speter
1596251881Speter          SVN_ERR(match_existing_target(&prop_matches, content, hunk,
1597251881Speter                                        scratch_pool));
1598251881Speter
1599251881Speter          if (prop_matches)
1600251881Speter            {
1601251881Speter              matched_line = 1;
1602251881Speter              already_applied = TRUE;
1603251881Speter            }
1604251881Speter          else
1605251881Speter            matched_line = 0; /* reject */
1606251881Speter        }
1607251881Speter      else
1608251881Speter        matched_line = 1;
1609251881Speter    }
1610251881Speter  else if (original_start > 0 && content->existed)
1611251881Speter    {
1612251881Speter      svn_linenum_t saved_line = content->current_line;
1613251881Speter
1614251881Speter      /* Scan for a match at the line where the hunk thinks it
1615251881Speter       * should be going. */
1616251881Speter      SVN_ERR(seek_to_line(content, original_start, scratch_pool));
1617251881Speter      if (content->current_line != original_start)
1618251881Speter        {
1619251881Speter          /* Seek failed. */
1620251881Speter          matched_line = 0;
1621251881Speter        }
1622251881Speter      else
1623251881Speter        SVN_ERR(scan_for_match(&matched_line, content, hunk, TRUE,
1624251881Speter                               original_start + 1, fuzz,
1625251881Speter                               ignore_whitespace, FALSE,
1626251881Speter                               cancel_func, cancel_baton,
1627251881Speter                               scratch_pool));
1628251881Speter
1629251881Speter      if (matched_line != original_start)
1630251881Speter        {
1631251881Speter          /* Check if the hunk is already applied.
1632251881Speter           * We only check for an exact match here, and don't bother checking
1633251881Speter           * for already applied patches with offset/fuzz, because such a
1634251881Speter           * check would be ambiguous. */
1635251881Speter          if (fuzz == 0)
1636251881Speter            {
1637251881Speter              svn_linenum_t modified_start;
1638251881Speter
1639251881Speter              modified_start = svn_diff_hunk_get_modified_start(hunk);
1640251881Speter              if (modified_start == 0)
1641251881Speter                {
1642251881Speter                  /* Patch wants to delete the file. */
1643251881Speter                  already_applied = target->locally_deleted;
1644251881Speter                }
1645251881Speter              else
1646251881Speter                {
1647251881Speter                  SVN_ERR(seek_to_line(content, modified_start,
1648251881Speter                                       scratch_pool));
1649251881Speter                  SVN_ERR(scan_for_match(&matched_line, content,
1650251881Speter                                         hunk, TRUE,
1651251881Speter                                         modified_start + 1,
1652251881Speter                                         fuzz, ignore_whitespace, TRUE,
1653251881Speter                                         cancel_func, cancel_baton,
1654251881Speter                                         scratch_pool));
1655251881Speter                  already_applied = (matched_line == modified_start);
1656251881Speter                }
1657251881Speter            }
1658251881Speter          else
1659251881Speter            already_applied = FALSE;
1660251881Speter
1661251881Speter          if (! already_applied)
1662251881Speter            {
1663251881Speter              /* Scan the whole file again from the start. */
1664251881Speter              SVN_ERR(seek_to_line(content, 1, scratch_pool));
1665251881Speter
1666251881Speter              /* Scan forward towards the hunk's line and look for a line
1667251881Speter               * where the hunk matches. */
1668251881Speter              SVN_ERR(scan_for_match(&matched_line, content, hunk, FALSE,
1669251881Speter                                     original_start, fuzz,
1670251881Speter                                     ignore_whitespace, FALSE,
1671251881Speter                                     cancel_func, cancel_baton,
1672251881Speter                                     scratch_pool));
1673251881Speter
1674251881Speter              /* In tie-break situations, we arbitrarily prefer early matches
1675251881Speter               * to save us from scanning the rest of the file. */
1676251881Speter              if (matched_line == 0)
1677251881Speter                {
1678251881Speter                  /* Scan forward towards the end of the file and look
1679251881Speter                   * for a line where the hunk matches. */
1680251881Speter                  SVN_ERR(scan_for_match(&matched_line, content, hunk,
1681251881Speter                                         TRUE, 0, fuzz, ignore_whitespace,
1682251881Speter                                         FALSE, cancel_func, cancel_baton,
1683251881Speter                                         scratch_pool));
1684251881Speter                }
1685251881Speter            }
1686251881Speter        }
1687251881Speter
1688251881Speter      SVN_ERR(seek_to_line(content, saved_line, scratch_pool));
1689251881Speter    }
1690251881Speter  else
1691251881Speter    {
1692251881Speter      /* The hunk wants to modify a file which doesn't exist. */
1693251881Speter      matched_line = 0;
1694251881Speter    }
1695251881Speter
1696251881Speter  (*hi) = apr_pcalloc(result_pool, sizeof(hunk_info_t));
1697251881Speter  (*hi)->hunk = hunk;
1698251881Speter  (*hi)->matched_line = matched_line;
1699251881Speter  (*hi)->rejected = (matched_line == 0);
1700251881Speter  (*hi)->already_applied = already_applied;
1701251881Speter  (*hi)->fuzz = fuzz;
1702251881Speter
1703251881Speter  return SVN_NO_ERROR;
1704251881Speter}
1705251881Speter
1706251881Speter/* Copy lines to the patched content until the specified LINE has been
1707251881Speter * reached. Indicate in *EOF whether end-of-file was encountered while
1708251881Speter * reading from the target.
1709251881Speter * If LINE is zero, copy lines until end-of-file has been reached.
1710251881Speter * Do all allocations in POOL. */
1711251881Speterstatic svn_error_t *
1712251881Spetercopy_lines_to_target(target_content_t *content, svn_linenum_t line,
1713251881Speter                     apr_pool_t *pool)
1714251881Speter{
1715251881Speter  apr_pool_t *iterpool;
1716251881Speter
1717251881Speter  iterpool = svn_pool_create(pool);
1718251881Speter  while ((content->current_line < line || line == 0) && ! content->eof)
1719251881Speter    {
1720251881Speter      const char *target_line;
1721251881Speter      apr_size_t len;
1722251881Speter
1723251881Speter      svn_pool_clear(iterpool);
1724251881Speter
1725251881Speter      SVN_ERR(readline(content, &target_line, iterpool, iterpool));
1726251881Speter      if (! content->eof)
1727251881Speter        target_line = apr_pstrcat(iterpool, target_line, content->eol_str,
1728251881Speter                                  (char *)NULL);
1729251881Speter      len = strlen(target_line);
1730251881Speter      SVN_ERR(content->write(content->write_baton, target_line,
1731251881Speter                             len, iterpool));
1732251881Speter    }
1733251881Speter  svn_pool_destroy(iterpool);
1734251881Speter
1735251881Speter  return SVN_NO_ERROR;
1736251881Speter}
1737251881Speter
1738251881Speter/* Write the diff text of HUNK to TARGET's reject file,
1739251881Speter * and mark TARGET as having had rejects.
1740251881Speter * We don't expand keywords, nor normalise line-endings, in reject files.
1741251881Speter * Do temporary allocations in SCRATCH_POOL. */
1742251881Speterstatic svn_error_t *
1743251881Speterreject_hunk(patch_target_t *target, target_content_t *content,
1744251881Speter            svn_diff_hunk_t *hunk, const char *prop_name,
1745251881Speter            apr_pool_t *pool)
1746251881Speter{
1747251881Speter  const char *hunk_header;
1748251881Speter  apr_size_t len;
1749251881Speter  svn_boolean_t eof;
1750251881Speter  static const char * const text_atat = "@@";
1751251881Speter  static const char * const prop_atat = "##";
1752251881Speter  const char *atat;
1753251881Speter  apr_pool_t *iterpool;
1754251881Speter
1755251881Speter  if (prop_name)
1756251881Speter    {
1757251881Speter      const char *prop_header;
1758251881Speter
1759251881Speter      /* ### Print 'Added', 'Deleted' or 'Modified' instead of 'Property'.
1760251881Speter       */
1761251881Speter      prop_header = apr_psprintf(pool, "Property: %s\n", prop_name);
1762251881Speter      len = strlen(prop_header);
1763251881Speter      SVN_ERR(svn_io_file_write_full(target->reject_file, prop_header,
1764251881Speter                                     len, &len, pool));
1765251881Speter      atat = prop_atat;
1766251881Speter    }
1767251881Speter  else
1768251881Speter    {
1769251881Speter      atat = text_atat;
1770251881Speter    }
1771251881Speter
1772251881Speter  hunk_header = apr_psprintf(pool, "%s -%lu,%lu +%lu,%lu %s%s",
1773251881Speter                             atat,
1774251881Speter                             svn_diff_hunk_get_original_start(hunk),
1775251881Speter                             svn_diff_hunk_get_original_length(hunk),
1776251881Speter                             svn_diff_hunk_get_modified_start(hunk),
1777251881Speter                             svn_diff_hunk_get_modified_length(hunk),
1778251881Speter                             atat,
1779251881Speter                             APR_EOL_STR);
1780251881Speter  len = strlen(hunk_header);
1781251881Speter  SVN_ERR(svn_io_file_write_full(target->reject_file, hunk_header, len,
1782251881Speter                                 &len, pool));
1783251881Speter
1784251881Speter  iterpool = svn_pool_create(pool);
1785251881Speter  do
1786251881Speter    {
1787251881Speter      svn_stringbuf_t *hunk_line;
1788251881Speter      const char *eol_str;
1789251881Speter
1790251881Speter      svn_pool_clear(iterpool);
1791251881Speter
1792251881Speter      SVN_ERR(svn_diff_hunk_readline_diff_text(hunk, &hunk_line, &eol_str,
1793251881Speter                                               &eof, iterpool, iterpool));
1794251881Speter      if (! eof)
1795251881Speter        {
1796251881Speter          if (hunk_line->len >= 1)
1797251881Speter            {
1798251881Speter              len = hunk_line->len;
1799251881Speter              SVN_ERR(svn_io_file_write_full(target->reject_file,
1800251881Speter                                             hunk_line->data, len, &len,
1801251881Speter                                             iterpool));
1802251881Speter            }
1803251881Speter
1804251881Speter          if (eol_str)
1805251881Speter            {
1806251881Speter              len = strlen(eol_str);
1807251881Speter              SVN_ERR(svn_io_file_write_full(target->reject_file, eol_str,
1808251881Speter                                             len, &len, iterpool));
1809251881Speter            }
1810251881Speter        }
1811251881Speter    }
1812251881Speter  while (! eof);
1813251881Speter  svn_pool_destroy(iterpool);
1814251881Speter
1815251881Speter  if (prop_name)
1816251881Speter    target->had_prop_rejects = TRUE;
1817251881Speter  else
1818251881Speter    target->had_rejects = TRUE;
1819251881Speter
1820251881Speter  return SVN_NO_ERROR;
1821251881Speter}
1822251881Speter
1823251881Speter/* Write the modified text of the hunk described by HI to the patched
1824251881Speter * CONTENT. TARGET is the patch target.
1825251881Speter * If PROP_NAME is not NULL, the hunk is assumed to be targeted for
1826251881Speter * a property with the given name.
1827251881Speter * Do temporary allocations in POOL. */
1828251881Speterstatic svn_error_t *
1829251881Speterapply_hunk(patch_target_t *target, target_content_t *content,
1830251881Speter           hunk_info_t *hi, const char *prop_name, apr_pool_t *pool)
1831251881Speter{
1832251881Speter  svn_linenum_t lines_read;
1833251881Speter  svn_boolean_t eof;
1834251881Speter  apr_pool_t *iterpool;
1835251881Speter
1836251881Speter  /* ### Is there a cleaner way to describe if we have an existing target?
1837251881Speter   */
1838251881Speter  if (target->kind_on_disk == svn_node_file || prop_name)
1839251881Speter    {
1840251881Speter      svn_linenum_t line;
1841251881Speter
1842251881Speter      /* Move forward to the hunk's line, copying data as we go.
1843251881Speter       * Also copy leading lines of context which matched with fuzz.
1844251881Speter       * The target has changed on the fuzzy-matched lines,
1845251881Speter       * so we should retain the target's version of those lines. */
1846251881Speter      SVN_ERR(copy_lines_to_target(content, hi->matched_line + hi->fuzz,
1847251881Speter                                   pool));
1848251881Speter
1849251881Speter      /* Skip the target's version of the hunk.
1850251881Speter       * Don't skip trailing lines which matched with fuzz. */
1851251881Speter      line = content->current_line +
1852251881Speter             svn_diff_hunk_get_original_length(hi->hunk) - (2 * hi->fuzz);
1853251881Speter      SVN_ERR(seek_to_line(content, line, pool));
1854251881Speter      if (content->current_line != line && ! content->eof)
1855251881Speter        {
1856251881Speter          /* Seek failed, reject this hunk. */
1857251881Speter          hi->rejected = TRUE;
1858251881Speter          SVN_ERR(reject_hunk(target, content, hi->hunk, prop_name, pool));
1859251881Speter          return SVN_NO_ERROR;
1860251881Speter        }
1861251881Speter    }
1862251881Speter
1863251881Speter  /* Write the hunk's version to the patched result.
1864251881Speter   * Don't write the lines which matched with fuzz. */
1865251881Speter  lines_read = 0;
1866251881Speter  svn_diff_hunk_reset_modified_text(hi->hunk);
1867251881Speter  iterpool = svn_pool_create(pool);
1868251881Speter  do
1869251881Speter    {
1870251881Speter      svn_stringbuf_t *hunk_line;
1871251881Speter      const char *eol_str;
1872251881Speter
1873251881Speter      svn_pool_clear(iterpool);
1874251881Speter
1875251881Speter      SVN_ERR(svn_diff_hunk_readline_modified_text(hi->hunk, &hunk_line,
1876251881Speter                                                   &eol_str, &eof,
1877251881Speter                                                   iterpool, iterpool));
1878251881Speter      lines_read++;
1879251881Speter      if (lines_read > hi->fuzz &&
1880251881Speter          lines_read <= svn_diff_hunk_get_modified_length(hi->hunk) - hi->fuzz)
1881251881Speter        {
1882251881Speter          apr_size_t len;
1883251881Speter
1884251881Speter          if (hunk_line->len >= 1)
1885251881Speter            {
1886251881Speter              len = hunk_line->len;
1887251881Speter              SVN_ERR(content->write(content->write_baton,
1888251881Speter                                     hunk_line->data, len, iterpool));
1889251881Speter            }
1890251881Speter
1891251881Speter          if (eol_str)
1892251881Speter            {
1893251881Speter              /* Use the EOL as it was read from the patch file,
1894251881Speter               * unless the target's EOL style is set by svn:eol-style */
1895251881Speter              if (content->eol_style != svn_subst_eol_style_none)
1896251881Speter                eol_str = content->eol_str;
1897251881Speter
1898251881Speter              len = strlen(eol_str);
1899251881Speter              SVN_ERR(content->write(content->write_baton,
1900251881Speter                                     eol_str, len, iterpool));
1901251881Speter            }
1902251881Speter        }
1903251881Speter    }
1904251881Speter  while (! eof);
1905251881Speter  svn_pool_destroy(iterpool);
1906251881Speter
1907251881Speter  if (prop_name)
1908251881Speter    target->has_prop_changes = TRUE;
1909251881Speter  else
1910251881Speter    target->has_text_changes = TRUE;
1911251881Speter
1912251881Speter  return SVN_NO_ERROR;
1913251881Speter}
1914251881Speter
1915251881Speter/* Use client context CTX to send a suitable notification for hunk HI,
1916251881Speter * using TARGET to determine the path. If the hunk is a property hunk,
1917251881Speter * PROP_NAME must be the name of the property, else NULL.
1918251881Speter * Use POOL for temporary allocations. */
1919251881Speterstatic svn_error_t *
1920251881Spetersend_hunk_notification(const hunk_info_t *hi,
1921251881Speter                       const patch_target_t *target,
1922251881Speter                       const char *prop_name,
1923251881Speter                       const svn_client_ctx_t *ctx,
1924251881Speter                       apr_pool_t *pool)
1925251881Speter{
1926251881Speter  svn_wc_notify_t *notify;
1927251881Speter  svn_wc_notify_action_t action;
1928251881Speter
1929251881Speter  if (hi->already_applied)
1930251881Speter    action = svn_wc_notify_patch_hunk_already_applied;
1931251881Speter  else if (hi->rejected)
1932251881Speter    action = svn_wc_notify_patch_rejected_hunk;
1933251881Speter  else
1934251881Speter    action = svn_wc_notify_patch_applied_hunk;
1935251881Speter
1936251881Speter  notify = svn_wc_create_notify(target->local_abspath
1937251881Speter                                    ? target->local_abspath
1938251881Speter                                    : target->local_relpath,
1939251881Speter                                action, pool);
1940251881Speter  notify->hunk_original_start =
1941251881Speter    svn_diff_hunk_get_original_start(hi->hunk);
1942251881Speter  notify->hunk_original_length =
1943251881Speter    svn_diff_hunk_get_original_length(hi->hunk);
1944251881Speter  notify->hunk_modified_start =
1945251881Speter    svn_diff_hunk_get_modified_start(hi->hunk);
1946251881Speter  notify->hunk_modified_length =
1947251881Speter    svn_diff_hunk_get_modified_length(hi->hunk);
1948251881Speter  notify->hunk_matched_line = hi->matched_line;
1949251881Speter  notify->hunk_fuzz = hi->fuzz;
1950251881Speter  notify->prop_name = prop_name;
1951251881Speter
1952251881Speter  (*ctx->notify_func2)(ctx->notify_baton2, notify, pool);
1953251881Speter
1954251881Speter  return SVN_NO_ERROR;
1955251881Speter}
1956251881Speter
1957251881Speter/* Use client context CTX to send a suitable notification for a patch TARGET.
1958251881Speter * Use POOL for temporary allocations. */
1959251881Speterstatic svn_error_t *
1960251881Spetersend_patch_notification(const patch_target_t *target,
1961251881Speter                        const svn_client_ctx_t *ctx,
1962251881Speter                        apr_pool_t *pool)
1963251881Speter{
1964251881Speter  svn_wc_notify_t *notify;
1965251881Speter  svn_wc_notify_action_t action;
1966251881Speter
1967251881Speter  if (! ctx->notify_func2)
1968251881Speter    return SVN_NO_ERROR;
1969251881Speter
1970251881Speter  if (target->skipped)
1971251881Speter    action = svn_wc_notify_skip;
1972251881Speter  else if (target->deleted)
1973251881Speter    action = svn_wc_notify_delete;
1974251881Speter  else if (target->added || target->replaced)
1975251881Speter    action = svn_wc_notify_add;
1976251881Speter  else
1977251881Speter    action = svn_wc_notify_patch;
1978251881Speter
1979251881Speter  notify = svn_wc_create_notify(target->local_abspath ? target->local_abspath
1980251881Speter                                                 : target->local_relpath,
1981251881Speter                                action, pool);
1982251881Speter  notify->kind = svn_node_file;
1983251881Speter
1984251881Speter  if (action == svn_wc_notify_skip)
1985251881Speter    {
1986251881Speter      if (target->db_kind == svn_node_none ||
1987251881Speter          target->db_kind == svn_node_unknown)
1988251881Speter        notify->content_state = svn_wc_notify_state_missing;
1989251881Speter      else if (target->db_kind == svn_node_dir)
1990251881Speter        notify->content_state = svn_wc_notify_state_obstructed;
1991251881Speter      else
1992251881Speter        notify->content_state = svn_wc_notify_state_unknown;
1993251881Speter    }
1994251881Speter  else
1995251881Speter    {
1996251881Speter      if (target->had_rejects)
1997251881Speter        notify->content_state = svn_wc_notify_state_conflicted;
1998251881Speter      else if (target->local_mods)
1999251881Speter        notify->content_state = svn_wc_notify_state_merged;
2000251881Speter      else if (target->has_text_changes)
2001251881Speter        notify->content_state = svn_wc_notify_state_changed;
2002251881Speter
2003251881Speter      if (target->had_prop_rejects)
2004251881Speter        notify->prop_state = svn_wc_notify_state_conflicted;
2005251881Speter      else if (target->has_prop_changes)
2006251881Speter        notify->prop_state = svn_wc_notify_state_changed;
2007251881Speter    }
2008251881Speter
2009251881Speter  (*ctx->notify_func2)(ctx->notify_baton2, notify, pool);
2010251881Speter
2011251881Speter  if (action == svn_wc_notify_patch)
2012251881Speter    {
2013251881Speter      int i;
2014251881Speter      apr_pool_t *iterpool;
2015251881Speter      apr_hash_index_t *hash_index;
2016251881Speter
2017251881Speter      iterpool = svn_pool_create(pool);
2018251881Speter      for (i = 0; i < target->content->hunks->nelts; i++)
2019251881Speter        {
2020251881Speter          const hunk_info_t *hi;
2021251881Speter
2022251881Speter          svn_pool_clear(iterpool);
2023251881Speter
2024251881Speter          hi = APR_ARRAY_IDX(target->content->hunks, i, hunk_info_t *);
2025251881Speter
2026251881Speter          SVN_ERR(send_hunk_notification(hi, target, NULL /* prop_name */,
2027251881Speter                                         ctx, iterpool));
2028251881Speter        }
2029251881Speter
2030251881Speter      for (hash_index = apr_hash_first(pool, target->prop_targets);
2031251881Speter           hash_index;
2032251881Speter           hash_index = apr_hash_next(hash_index))
2033251881Speter        {
2034251881Speter          prop_patch_target_t *prop_target;
2035251881Speter
2036251881Speter          prop_target = svn__apr_hash_index_val(hash_index);
2037251881Speter
2038251881Speter          for (i = 0; i < prop_target->content->hunks->nelts; i++)
2039251881Speter            {
2040251881Speter              const hunk_info_t *hi;
2041251881Speter
2042251881Speter              svn_pool_clear(iterpool);
2043251881Speter
2044251881Speter              hi = APR_ARRAY_IDX(prop_target->content->hunks, i,
2045251881Speter                                 hunk_info_t *);
2046251881Speter
2047251881Speter              /* Don't notify on the hunk level for added or deleted props. */
2048251881Speter              if (prop_target->operation != svn_diff_op_added &&
2049251881Speter                  prop_target->operation != svn_diff_op_deleted)
2050251881Speter                SVN_ERR(send_hunk_notification(hi, target, prop_target->name,
2051251881Speter                                               ctx, iterpool));
2052251881Speter            }
2053251881Speter        }
2054251881Speter      svn_pool_destroy(iterpool);
2055251881Speter    }
2056251881Speter
2057251881Speter  return SVN_NO_ERROR;
2058251881Speter}
2059251881Speter
2060289166Speterstatic void
2061289166Spetersvn_sort__array(apr_array_header_t *array,
2062289166Speter                int (*comparison_func)(const void *,
2063289166Speter                                       const void *))
2064289166Speter{
2065289166Speter  qsort(array->elts, array->nelts, array->elt_size, comparison_func);
2066289166Speter}
2067289166Speter
2068289166Speter/* Implements the callback for svn_sort__array.  Puts hunks that match
2069289166Speter   before hunks that do not match, puts hunks that match in order
2070289166Speter   based on postion matched, puts hunks that do not match in order
2071289166Speter   based on original position. */
2072289166Speterstatic int
2073289166Spetersort_matched_hunks(const void *a, const void *b)
2074289166Speter{
2075289166Speter  const hunk_info_t *item1 = *((const hunk_info_t * const *)a);
2076289166Speter  const hunk_info_t *item2 = *((const hunk_info_t * const *)b);
2077289166Speter  svn_boolean_t matched1 = !item1->rejected && !item1->already_applied;
2078289166Speter  svn_boolean_t matched2 = !item2->rejected && !item2->already_applied;
2079289166Speter  svn_linenum_t original1, original2;
2080289166Speter
2081289166Speter  if (matched1 && matched2)
2082289166Speter    {
2083289166Speter      /* Both match so use order matched in file. */
2084289166Speter      if (item1->matched_line > item2->matched_line)
2085289166Speter        return 1;
2086289166Speter      else if (item1->matched_line == item2->matched_line)
2087289166Speter        return 0;
2088289166Speter      else
2089289166Speter        return -1;
2090289166Speter    }
2091289166Speter  else if (matched2)
2092289166Speter    /* Only second matches, put it before first. */
2093289166Speter    return 1;
2094289166Speter  else if (matched1)
2095289166Speter    /* Only first matches, put it before second. */
2096289166Speter    return -1;
2097289166Speter
2098289166Speter  /* Neither matches, sort by original_start. */
2099289166Speter  original1 = svn_diff_hunk_get_original_start(item1->hunk);
2100289166Speter  original2 = svn_diff_hunk_get_original_start(item2->hunk);
2101289166Speter  if (original1 > original2)
2102289166Speter    return 1;
2103289166Speter  else if (original1 == original2)
2104289166Speter    return 0;
2105289166Speter  else
2106289166Speter    return -1;
2107289166Speter}
2108289166Speter
2109289166Speter
2110251881Speter/* Apply a PATCH to a working copy at ABS_WC_PATH and put the result
2111251881Speter * into temporary files, to be installed in the working copy later.
2112251881Speter * Return information about the patch target in *PATCH_TARGET, allocated
2113251881Speter * in RESULT_POOL. Use WC_CTX as the working copy context.
2114251881Speter * STRIP_COUNT specifies the number of leading path components
2115251881Speter * which should be stripped from target paths in the patch.
2116251881Speter * REMOVE_TEMPFILES, PATCH_FUNC, and PATCH_BATON as in svn_client_patch().
2117251881Speter * IGNORE_WHITESPACE tells whether whitespace should be considered when
2118251881Speter * doing the matching.
2119251881Speter * Call cancel CANCEL_FUNC with baton CANCEL_BATON to trigger cancellation.
2120251881Speter * Do temporary allocations in SCRATCH_POOL. */
2121251881Speterstatic svn_error_t *
2122251881Speterapply_one_patch(patch_target_t **patch_target, svn_patch_t *patch,
2123251881Speter                const char *abs_wc_path, svn_wc_context_t *wc_ctx,
2124251881Speter                int strip_count,
2125251881Speter                svn_boolean_t ignore_whitespace,
2126251881Speter                svn_boolean_t remove_tempfiles,
2127251881Speter                svn_client_patch_func_t patch_func,
2128251881Speter                void *patch_baton,
2129251881Speter                svn_cancel_func_t cancel_func,
2130251881Speter                void *cancel_baton,
2131251881Speter                apr_pool_t *result_pool, apr_pool_t *scratch_pool)
2132251881Speter{
2133251881Speter  patch_target_t *target;
2134251881Speter  apr_pool_t *iterpool;
2135251881Speter  int i;
2136251881Speter  static const svn_linenum_t MAX_FUZZ = 2;
2137251881Speter  apr_hash_index_t *hash_index;
2138251881Speter
2139251881Speter  SVN_ERR(init_patch_target(&target, patch, abs_wc_path, wc_ctx, strip_count,
2140251881Speter                            remove_tempfiles, result_pool, scratch_pool));
2141251881Speter  if (target->skipped)
2142251881Speter    {
2143251881Speter      *patch_target = target;
2144251881Speter      return SVN_NO_ERROR;
2145251881Speter    }
2146251881Speter
2147251881Speter  if (patch_func)
2148251881Speter    {
2149251881Speter      SVN_ERR(patch_func(patch_baton, &target->filtered,
2150251881Speter                         target->canon_path_from_patchfile,
2151251881Speter                         target->patched_path, target->reject_path,
2152251881Speter                         scratch_pool));
2153251881Speter      if (target->filtered)
2154251881Speter        {
2155251881Speter          *patch_target = target;
2156251881Speter          return SVN_NO_ERROR;
2157251881Speter        }
2158251881Speter    }
2159251881Speter
2160251881Speter  iterpool = svn_pool_create(scratch_pool);
2161251881Speter  /* Match hunks. */
2162251881Speter  for (i = 0; i < patch->hunks->nelts; i++)
2163251881Speter    {
2164251881Speter      svn_diff_hunk_t *hunk;
2165251881Speter      hunk_info_t *hi;
2166251881Speter      svn_linenum_t fuzz = 0;
2167251881Speter
2168251881Speter      svn_pool_clear(iterpool);
2169251881Speter
2170251881Speter      if (cancel_func)
2171251881Speter        SVN_ERR(cancel_func(cancel_baton));
2172251881Speter
2173251881Speter      hunk = APR_ARRAY_IDX(patch->hunks, i, svn_diff_hunk_t *);
2174251881Speter
2175251881Speter      /* Determine the line the hunk should be applied at.
2176251881Speter       * If no match is found initially, try with fuzz. */
2177251881Speter      do
2178251881Speter        {
2179251881Speter          SVN_ERR(get_hunk_info(&hi, target, target->content, hunk, fuzz,
2180251881Speter                                ignore_whitespace,
2181251881Speter                                FALSE /* is_prop_hunk */,
2182251881Speter                                cancel_func, cancel_baton,
2183251881Speter                                result_pool, iterpool));
2184251881Speter          fuzz++;
2185251881Speter        }
2186251881Speter      while (hi->rejected && fuzz <= MAX_FUZZ && ! hi->already_applied);
2187251881Speter
2188251881Speter      APR_ARRAY_PUSH(target->content->hunks, hunk_info_t *) = hi;
2189251881Speter    }
2190251881Speter
2191289166Speter  /* Hunks are applied in the order determined by the matched line and
2192289166Speter     this may be different from the order of the original lines. */
2193289166Speter  svn_sort__array(target->content->hunks, sort_matched_hunks);
2194289166Speter
2195251881Speter  /* Apply or reject hunks. */
2196251881Speter  for (i = 0; i < target->content->hunks->nelts; i++)
2197251881Speter    {
2198251881Speter      hunk_info_t *hi;
2199251881Speter
2200251881Speter      svn_pool_clear(iterpool);
2201251881Speter
2202251881Speter      if (cancel_func)
2203251881Speter        SVN_ERR(cancel_func(cancel_baton));
2204251881Speter
2205251881Speter      hi = APR_ARRAY_IDX(target->content->hunks, i, hunk_info_t *);
2206251881Speter      if (hi->already_applied)
2207251881Speter        continue;
2208251881Speter      else if (hi->rejected)
2209251881Speter        SVN_ERR(reject_hunk(target, target->content, hi->hunk,
2210251881Speter                            NULL /* prop_name */,
2211251881Speter                            iterpool));
2212251881Speter      else
2213251881Speter        SVN_ERR(apply_hunk(target, target->content, hi,
2214251881Speter                           NULL /* prop_name */,  iterpool));
2215251881Speter    }
2216251881Speter
2217251881Speter  if (target->kind_on_disk == svn_node_file)
2218251881Speter    {
2219251881Speter      /* Copy any remaining lines to target. */
2220251881Speter      SVN_ERR(copy_lines_to_target(target->content, 0, scratch_pool));
2221251881Speter      if (! target->content->eof)
2222251881Speter        {
2223251881Speter          /* We could not copy the entire target file to the temporary file,
2224251881Speter           * and would truncate the target if we copied the temporary file
2225251881Speter           * on top of it. Skip this target. */
2226251881Speter          target->skipped = TRUE;
2227251881Speter        }
2228251881Speter    }
2229251881Speter
2230251881Speter  /* Match property hunks. */
2231251881Speter  for (hash_index = apr_hash_first(scratch_pool, patch->prop_patches);
2232251881Speter       hash_index;
2233251881Speter       hash_index = apr_hash_next(hash_index))
2234251881Speter    {
2235251881Speter      svn_prop_patch_t *prop_patch;
2236251881Speter      const char *prop_name;
2237251881Speter      prop_patch_target_t *prop_target;
2238251881Speter
2239251881Speter      prop_name = svn__apr_hash_index_key(hash_index);
2240251881Speter      prop_patch = svn__apr_hash_index_val(hash_index);
2241251881Speter
2242251881Speter      if (! strcmp(prop_name, SVN_PROP_SPECIAL))
2243251881Speter        target->is_special = TRUE;
2244251881Speter
2245251881Speter      /* We'll store matched hunks in prop_content. */
2246251881Speter      prop_target = svn_hash_gets(target->prop_targets, prop_name);
2247251881Speter
2248251881Speter      for (i = 0; i < prop_patch->hunks->nelts; i++)
2249251881Speter        {
2250251881Speter          svn_diff_hunk_t *hunk;
2251251881Speter          hunk_info_t *hi;
2252251881Speter          svn_linenum_t fuzz = 0;
2253251881Speter
2254251881Speter          svn_pool_clear(iterpool);
2255251881Speter
2256251881Speter          if (cancel_func)
2257251881Speter            SVN_ERR(cancel_func(cancel_baton));
2258251881Speter
2259251881Speter          hunk = APR_ARRAY_IDX(prop_patch->hunks, i, svn_diff_hunk_t *);
2260251881Speter
2261251881Speter          /* Determine the line the hunk should be applied at.
2262251881Speter           * If no match is found initially, try with fuzz. */
2263251881Speter          do
2264251881Speter            {
2265251881Speter              SVN_ERR(get_hunk_info(&hi, target, prop_target->content,
2266251881Speter                                    hunk, fuzz,
2267251881Speter                                    ignore_whitespace,
2268251881Speter                                    TRUE /* is_prop_hunk */,
2269251881Speter                                    cancel_func, cancel_baton,
2270251881Speter                                    result_pool, iterpool));
2271251881Speter              fuzz++;
2272251881Speter            }
2273251881Speter          while (hi->rejected && fuzz <= MAX_FUZZ && ! hi->already_applied);
2274251881Speter
2275251881Speter          APR_ARRAY_PUSH(prop_target->content->hunks, hunk_info_t *) = hi;
2276251881Speter        }
2277251881Speter    }
2278251881Speter
2279251881Speter  /* Apply or reject property hunks. */
2280251881Speter  for (hash_index = apr_hash_first(scratch_pool, target->prop_targets);
2281251881Speter       hash_index;
2282251881Speter       hash_index = apr_hash_next(hash_index))
2283251881Speter    {
2284251881Speter      prop_patch_target_t *prop_target;
2285251881Speter
2286251881Speter      prop_target = svn__apr_hash_index_val(hash_index);
2287251881Speter
2288251881Speter      for (i = 0; i < prop_target->content->hunks->nelts; i++)
2289251881Speter        {
2290251881Speter          hunk_info_t *hi;
2291251881Speter
2292251881Speter          svn_pool_clear(iterpool);
2293251881Speter
2294251881Speter          hi = APR_ARRAY_IDX(prop_target->content->hunks, i,
2295251881Speter                             hunk_info_t *);
2296251881Speter          if (hi->already_applied)
2297251881Speter            continue;
2298251881Speter          else if (hi->rejected)
2299251881Speter            SVN_ERR(reject_hunk(target, prop_target->content, hi->hunk,
2300251881Speter                                prop_target->name,
2301251881Speter                                iterpool));
2302251881Speter          else
2303251881Speter            SVN_ERR(apply_hunk(target, prop_target->content, hi,
2304251881Speter                               prop_target->name,
2305251881Speter                               iterpool));
2306251881Speter        }
2307251881Speter
2308251881Speter        if (prop_target->content->existed)
2309251881Speter          {
2310251881Speter            /* Copy any remaining lines to target. */
2311251881Speter            SVN_ERR(copy_lines_to_target(prop_target->content, 0,
2312251881Speter                                         scratch_pool));
2313251881Speter            if (! prop_target->content->eof)
2314251881Speter              {
2315251881Speter                /* We could not copy the entire target property to the
2316251881Speter                 * temporary file, and would truncate the target if we
2317251881Speter                 * copied the temporary file on top of it. Skip this target.  */
2318251881Speter                target->skipped = TRUE;
2319251881Speter              }
2320251881Speter          }
2321251881Speter      }
2322251881Speter
2323251881Speter  svn_pool_destroy(iterpool);
2324251881Speter
2325251881Speter  if (!target->is_symlink)
2326251881Speter    {
2327251881Speter      /* Now close files we don't need any longer to get their contents
2328251881Speter       * flushed to disk.
2329251881Speter       * But we're not closing the reject file -- it still needed and
2330251881Speter       * will be closed later in write_out_rejected_hunks(). */
2331251881Speter      if (target->kind_on_disk == svn_node_file)
2332251881Speter        SVN_ERR(svn_io_file_close(target->file, scratch_pool));
2333251881Speter
2334251881Speter      SVN_ERR(svn_io_file_close(target->patched_file, scratch_pool));
2335251881Speter    }
2336251881Speter
2337251881Speter  if (! target->skipped)
2338251881Speter    {
2339251881Speter      apr_finfo_t working_file;
2340251881Speter      apr_finfo_t patched_file;
2341251881Speter
2342251881Speter      /* Get sizes of the patched temporary file and the working file.
2343251881Speter       * We'll need those to figure out whether we should delete the
2344251881Speter       * patched file. */
2345251881Speter      SVN_ERR(svn_io_stat(&patched_file, target->patched_path,
2346251881Speter                          APR_FINFO_SIZE | APR_FINFO_LINK, scratch_pool));
2347251881Speter      if (target->kind_on_disk == svn_node_file)
2348251881Speter        SVN_ERR(svn_io_stat(&working_file, target->local_abspath,
2349251881Speter                            APR_FINFO_SIZE | APR_FINFO_LINK, scratch_pool));
2350251881Speter      else
2351251881Speter        working_file.size = 0;
2352251881Speter
2353251881Speter      if (patched_file.size == 0 && working_file.size > 0)
2354251881Speter        {
2355251881Speter          /* If a unidiff removes all lines from a file, that usually
2356251881Speter           * means deletion, so we can confidently schedule the target
2357251881Speter           * for deletion. In the rare case where the unidiff was really
2358251881Speter           * meant to replace a file with an empty one, this may not
2359251881Speter           * be desirable. But the deletion can easily be reverted and
2360251881Speter           * creating an empty file manually is not exactly hard either. */
2361251881Speter          target->deleted = (target->db_kind == svn_node_file);
2362251881Speter        }
2363251881Speter      else if (patched_file.size == 0 && working_file.size == 0)
2364251881Speter        {
2365251881Speter          /* The target was empty or non-existent to begin with
2366251881Speter           * and no content was changed by patching.
2367251881Speter           * Report this as skipped if it didn't exist, unless in the special
2368251881Speter           * case of adding an empty file which has properties set on it or
2369251881Speter           * adding an empty file with a 'git diff' */
2370251881Speter          if (target->kind_on_disk == svn_node_none
2371251881Speter              && ! target->has_prop_changes
2372251881Speter              && ! target->added)
2373251881Speter            target->skipped = TRUE;
2374251881Speter        }
2375251881Speter      else if (patched_file.size > 0 && working_file.size == 0)
2376251881Speter        {
2377251881Speter          /* The patch has created a file. */
2378251881Speter          if (target->locally_deleted)
2379251881Speter            target->replaced = TRUE;
2380251881Speter          else if (target->db_kind == svn_node_none)
2381251881Speter            target->added = TRUE;
2382251881Speter        }
2383251881Speter    }
2384251881Speter
2385251881Speter  *patch_target = target;
2386251881Speter
2387251881Speter  return SVN_NO_ERROR;
2388251881Speter}
2389251881Speter
2390251881Speter/* Try to create missing parent directories for TARGET in the working copy
2391251881Speter * rooted at ABS_WC_PATH, and add the parents to version control.
2392251881Speter * If the parents cannot be created, mark the target as skipped.
2393251881Speter * Use client context CTX. If DRY_RUN is true, do not create missing
2394251881Speter * parents but issue notifications only.
2395251881Speter * Use SCRATCH_POOL for temporary allocations. */
2396251881Speterstatic svn_error_t *
2397251881Spetercreate_missing_parents(patch_target_t *target,
2398251881Speter                       const char *abs_wc_path,
2399251881Speter                       svn_client_ctx_t *ctx,
2400251881Speter                       svn_boolean_t dry_run,
2401251881Speter                       apr_pool_t *scratch_pool)
2402251881Speter{
2403251881Speter  const char *local_abspath;
2404251881Speter  apr_array_header_t *components;
2405251881Speter  int present_components;
2406251881Speter  int i;
2407251881Speter  apr_pool_t *iterpool;
2408251881Speter
2409251881Speter  /* Check if we can safely create the target's parent. */
2410251881Speter  local_abspath = abs_wc_path;
2411251881Speter  components = svn_path_decompose(target->local_relpath, scratch_pool);
2412251881Speter  present_components = 0;
2413251881Speter  iterpool = svn_pool_create(scratch_pool);
2414251881Speter  for (i = 0; i < components->nelts - 1; i++)
2415251881Speter    {
2416251881Speter      const char *component;
2417251881Speter      svn_node_kind_t wc_kind, disk_kind;
2418251881Speter
2419251881Speter      svn_pool_clear(iterpool);
2420251881Speter
2421251881Speter      component = APR_ARRAY_IDX(components, i, const char *);
2422251881Speter      local_abspath = svn_dirent_join(local_abspath, component, scratch_pool);
2423251881Speter
2424251881Speter      SVN_ERR(svn_wc_read_kind2(&wc_kind, ctx->wc_ctx, local_abspath,
2425251881Speter                                FALSE, TRUE, iterpool));
2426251881Speter
2427251881Speter      SVN_ERR(svn_io_check_path(local_abspath, &disk_kind, iterpool));
2428251881Speter
2429251881Speter      if (disk_kind == svn_node_file || wc_kind == svn_node_file)
2430251881Speter        {
2431251881Speter          /* on-disk files and missing files are obstructions */
2432251881Speter          target->skipped = TRUE;
2433251881Speter          break;
2434251881Speter        }
2435251881Speter      else if (disk_kind == svn_node_dir)
2436251881Speter        {
2437251881Speter          if (wc_kind == svn_node_dir)
2438251881Speter            present_components++;
2439251881Speter          else
2440251881Speter            {
2441251881Speter              target->skipped = TRUE;
2442251881Speter              break;
2443251881Speter            }
2444251881Speter        }
2445251881Speter      else if (wc_kind != svn_node_none)
2446251881Speter        {
2447251881Speter          /* Node is missing */
2448251881Speter          target->skipped = TRUE;
2449251881Speter          break;
2450251881Speter        }
2451251881Speter      else
2452251881Speter        {
2453251881Speter          /* It's not a file, it's not a dir...
2454251881Speter             Let's add a dir */
2455251881Speter          break;
2456251881Speter        }
2457251881Speter    }
2458251881Speter  if (! target->skipped)
2459251881Speter    {
2460251881Speter      local_abspath = abs_wc_path;
2461251881Speter      for (i = 0; i < present_components; i++)
2462251881Speter        {
2463251881Speter          const char *component;
2464251881Speter          component = APR_ARRAY_IDX(components, i, const char *);
2465251881Speter          local_abspath = svn_dirent_join(local_abspath,
2466251881Speter                                          component, scratch_pool);
2467251881Speter        }
2468251881Speter
2469251881Speter      if (!dry_run && present_components < components->nelts - 1)
2470251881Speter        SVN_ERR(svn_io_make_dir_recursively(
2471251881Speter                        svn_dirent_join(
2472251881Speter                                   abs_wc_path,
2473251881Speter                                   svn_relpath_dirname(target->local_relpath,
2474251881Speter                                                       scratch_pool),
2475251881Speter                                   scratch_pool),
2476251881Speter                        scratch_pool));
2477251881Speter
2478251881Speter      for (i = present_components; i < components->nelts - 1; i++)
2479251881Speter        {
2480251881Speter          const char *component;
2481251881Speter
2482251881Speter          svn_pool_clear(iterpool);
2483251881Speter
2484251881Speter          component = APR_ARRAY_IDX(components, i, const char *);
2485251881Speter          local_abspath = svn_dirent_join(local_abspath, component,
2486251881Speter                                          scratch_pool);
2487251881Speter          if (dry_run)
2488251881Speter            {
2489251881Speter              if (ctx->notify_func2)
2490251881Speter                {
2491251881Speter                  /* Just do notification. */
2492251881Speter                  svn_wc_notify_t *notify;
2493251881Speter                  notify = svn_wc_create_notify(local_abspath,
2494251881Speter                                                svn_wc_notify_add,
2495251881Speter                                                iterpool);
2496251881Speter                  notify->kind = svn_node_dir;
2497251881Speter                  ctx->notify_func2(ctx->notify_baton2, notify,
2498251881Speter                                    iterpool);
2499251881Speter                }
2500251881Speter            }
2501251881Speter          else
2502251881Speter            {
2503251881Speter              /* Create the missing component and add it
2504251881Speter               * to version control. Allow cancellation since we
2505251881Speter               * have not modified the working copy yet for this
2506251881Speter               * target. */
2507251881Speter
2508251881Speter              if (ctx->cancel_func)
2509251881Speter                SVN_ERR(ctx->cancel_func(ctx->cancel_baton));
2510251881Speter
2511251881Speter              SVN_ERR(svn_wc_add_from_disk2(ctx->wc_ctx, local_abspath,
2512251881Speter                                            NULL /*props*/,
2513251881Speter                                            ctx->notify_func2, ctx->notify_baton2,
2514251881Speter                                            iterpool));
2515251881Speter            }
2516251881Speter        }
2517251881Speter    }
2518251881Speter
2519251881Speter  svn_pool_destroy(iterpool);
2520251881Speter  return SVN_NO_ERROR;
2521251881Speter}
2522251881Speter
2523251881Speter/* Install a patched TARGET into the working copy at ABS_WC_PATH.
2524251881Speter * Use client context CTX to retrieve WC_CTX, and possibly doing
2525251881Speter * notifications. If DRY_RUN is TRUE, don't modify the working copy.
2526251881Speter * Do temporary allocations in POOL. */
2527251881Speterstatic svn_error_t *
2528251881Speterinstall_patched_target(patch_target_t *target, const char *abs_wc_path,
2529251881Speter                       svn_client_ctx_t *ctx, svn_boolean_t dry_run,
2530251881Speter                       apr_pool_t *pool)
2531251881Speter{
2532251881Speter  if (target->deleted)
2533251881Speter    {
2534251881Speter      if (! dry_run)
2535251881Speter        {
2536251881Speter          /* Schedule the target for deletion.  Suppress
2537251881Speter           * notification, we'll do it manually in a minute
2538251881Speter           * because we also need to notify during dry-run.
2539251881Speter           * Also suppress cancellation, because we'd rather
2540251881Speter           * notify about what we did before aborting. */
2541251881Speter          SVN_ERR(svn_wc_delete4(ctx->wc_ctx, target->local_abspath,
2542251881Speter                                 FALSE /* keep_local */, FALSE,
2543251881Speter                                 NULL, NULL, NULL, NULL, pool));
2544251881Speter        }
2545251881Speter    }
2546251881Speter  else
2547251881Speter    {
2548251881Speter      svn_node_kind_t parent_db_kind;
2549251881Speter      if (target->added || target->replaced)
2550251881Speter        {
2551251881Speter          const char *parent_abspath;
2552251881Speter
2553251881Speter          parent_abspath = svn_dirent_dirname(target->local_abspath,
2554251881Speter                                              pool);
2555251881Speter          /* If the target's parent directory does not yet exist
2556251881Speter           * we need to create it before we can copy the patched
2557251881Speter           * result in place. */
2558251881Speter          SVN_ERR(svn_wc_read_kind2(&parent_db_kind, ctx->wc_ctx,
2559251881Speter                                    parent_abspath, FALSE, FALSE, pool));
2560251881Speter
2561251881Speter          /* We can't add targets under nodes scheduled for delete, so add
2562251881Speter             a new directory if needed. */
2563251881Speter          if (parent_db_kind == svn_node_dir
2564251881Speter              || parent_db_kind == svn_node_file)
2565251881Speter            {
2566251881Speter              if (parent_db_kind != svn_node_dir)
2567251881Speter                target->skipped = TRUE;
2568251881Speter              else
2569251881Speter                {
2570251881Speter                  svn_node_kind_t disk_kind;
2571251881Speter
2572251881Speter                  SVN_ERR(svn_io_check_path(parent_abspath, &disk_kind, pool));
2573251881Speter                  if (disk_kind != svn_node_dir)
2574251881Speter                    target->skipped = TRUE;
2575251881Speter                }
2576251881Speter            }
2577251881Speter          else
2578251881Speter            SVN_ERR(create_missing_parents(target, abs_wc_path, ctx,
2579251881Speter                                           dry_run, pool));
2580251881Speter
2581251881Speter        }
2582251881Speter      else
2583251881Speter        {
2584251881Speter          svn_node_kind_t wc_kind;
2585251881Speter
2586251881Speter          /* The target should exist */
2587251881Speter          SVN_ERR(svn_wc_read_kind2(&wc_kind, ctx->wc_ctx,
2588251881Speter                                    target->local_abspath,
2589251881Speter                                    FALSE, FALSE, pool));
2590251881Speter
2591251881Speter          if (target->kind_on_disk == svn_node_none
2592251881Speter              || wc_kind != target->kind_on_disk)
2593251881Speter            {
2594251881Speter              target->skipped = TRUE;
2595251881Speter            }
2596251881Speter        }
2597251881Speter
2598251881Speter      if (! dry_run && ! target->skipped)
2599251881Speter        {
2600251881Speter          if (target->is_special)
2601251881Speter            {
2602251881Speter              svn_stream_t *stream;
2603251881Speter              svn_stream_t *patched_stream;
2604251881Speter
2605251881Speter              SVN_ERR(svn_stream_open_readonly(&patched_stream,
2606251881Speter                                               target->patched_path,
2607251881Speter                                               pool, pool));
2608251881Speter              SVN_ERR(svn_subst_create_specialfile(&stream,
2609251881Speter                                                   target->local_abspath,
2610251881Speter                                                   pool, pool));
2611251881Speter              SVN_ERR(svn_stream_copy3(patched_stream, stream,
2612251881Speter                                       ctx->cancel_func, ctx->cancel_baton,
2613251881Speter                                       pool));
2614251881Speter            }
2615251881Speter          else
2616251881Speter            {
2617251881Speter              svn_boolean_t repair_eol;
2618251881Speter
2619251881Speter              /* Copy the patched file on top of the target file.
2620251881Speter               * Always expand keywords in the patched file, but repair EOL
2621251881Speter               * only if svn:eol-style dictates a particular style. */
2622251881Speter              repair_eol = (target->content->eol_style ==
2623251881Speter                              svn_subst_eol_style_fixed ||
2624251881Speter                            target->content->eol_style ==
2625251881Speter                              svn_subst_eol_style_native);
2626251881Speter
2627251881Speter              SVN_ERR(svn_subst_copy_and_translate4(
2628251881Speter                        target->patched_path, target->local_abspath,
2629251881Speter                        target->content->eol_str, repair_eol,
2630251881Speter                        target->content->keywords,
2631251881Speter                        TRUE /* expand */, FALSE /* special */,
2632251881Speter                        ctx->cancel_func, ctx->cancel_baton, pool));
2633251881Speter            }
2634251881Speter
2635251881Speter          if (target->added || target->replaced)
2636251881Speter            {
2637251881Speter              /* The target file didn't exist previously,
2638251881Speter               * so add it to version control.
2639251881Speter               * Suppress notification, we'll do that later (and also
2640251881Speter               * during dry-run). Don't allow cancellation because
2641251881Speter               * we'd rather notify about what we did before aborting. */
2642251881Speter              SVN_ERR(svn_wc_add_from_disk2(ctx->wc_ctx, target->local_abspath,
2643251881Speter                                            NULL /*props*/,
2644251881Speter                                            NULL, NULL, pool));
2645251881Speter            }
2646251881Speter
2647251881Speter          /* Restore the target's executable bit if necessary. */
2648251881Speter          SVN_ERR(svn_io_set_file_executable(target->local_abspath,
2649251881Speter                                             target->executable,
2650251881Speter                                             FALSE, pool));
2651251881Speter        }
2652251881Speter    }
2653251881Speter
2654251881Speter  return SVN_NO_ERROR;
2655251881Speter}
2656251881Speter
2657251881Speter/* Write out rejected hunks, if any, to TARGET->REJECT_PATH. If DRY_RUN is
2658251881Speter * TRUE, don't modify the working copy.
2659251881Speter * Do temporary allocations in POOL.
2660251881Speter */
2661251881Speterstatic svn_error_t *
2662251881Speterwrite_out_rejected_hunks(patch_target_t *target,
2663251881Speter                         svn_boolean_t dry_run,
2664251881Speter                         apr_pool_t *pool)
2665251881Speter{
2666251881Speter  SVN_ERR(svn_io_file_close(target->reject_file, pool));
2667251881Speter
2668251881Speter  if (! dry_run && (target->had_rejects || target->had_prop_rejects))
2669251881Speter    {
2670251881Speter      /* Write out rejected hunks, if any. */
2671251881Speter      SVN_ERR(svn_io_copy_file(target->reject_path,
2672251881Speter                               apr_psprintf(pool, "%s.svnpatch.rej",
2673251881Speter                               target->local_abspath),
2674251881Speter                               FALSE, pool));
2675251881Speter      /* ### TODO mark file as conflicted. */
2676251881Speter    }
2677251881Speter  return SVN_NO_ERROR;
2678251881Speter}
2679251881Speter
2680251881Speter/* Install the patched properties for TARGET. Use client context CTX to
2681251881Speter * retrieve WC_CTX. If DRY_RUN is TRUE, don't modify the working copy.
2682251881Speter * Do temporary allocations in SCRATCH_POOL. */
2683251881Speterstatic svn_error_t *
2684251881Speterinstall_patched_prop_targets(patch_target_t *target,
2685251881Speter                             svn_client_ctx_t *ctx, svn_boolean_t dry_run,
2686251881Speter                             apr_pool_t *scratch_pool)
2687251881Speter{
2688251881Speter  apr_hash_index_t *hi;
2689251881Speter  apr_pool_t *iterpool;
2690251881Speter
2691251881Speter  iterpool = svn_pool_create(scratch_pool);
2692251881Speter
2693251881Speter  for (hi = apr_hash_first(scratch_pool, target->prop_targets);
2694251881Speter       hi;
2695251881Speter       hi = apr_hash_next(hi))
2696251881Speter    {
2697251881Speter      prop_patch_target_t *prop_target = svn__apr_hash_index_val(hi);
2698251881Speter      const svn_string_t *prop_val;
2699251881Speter      svn_error_t *err;
2700251881Speter
2701251881Speter      svn_pool_clear(iterpool);
2702251881Speter
2703251881Speter      if (ctx->cancel_func)
2704251881Speter        SVN_ERR(ctx->cancel_func(ctx->cancel_baton));
2705251881Speter
2706251881Speter      /* For a deleted prop we only set the value to NULL. */
2707251881Speter      if (prop_target->operation == svn_diff_op_deleted)
2708251881Speter        {
2709251881Speter          if (! dry_run)
2710251881Speter            SVN_ERR(svn_wc_prop_set4(ctx->wc_ctx, target->local_abspath,
2711251881Speter                                     prop_target->name, NULL, svn_depth_empty,
2712251881Speter                                     TRUE /* skip_checks */,
2713251881Speter                                     NULL /* changelist_filter */,
2714251881Speter                                     NULL, NULL /* cancellation */,
2715251881Speter                                     NULL, NULL /* notification */,
2716251881Speter                                     iterpool));
2717251881Speter          continue;
2718251881Speter        }
2719251881Speter
2720251881Speter      /* If the patch target doesn't exist yet, the patch wants to add an
2721251881Speter       * empty file with properties set on it. So create an empty file and
2722251881Speter       * add it to version control. But if the patch was in the 'git format'
2723251881Speter       * then the file has already been added.
2724251881Speter       *
2725251881Speter       * ### How can we tell whether the patch really wanted to create
2726251881Speter       * ### an empty directory? */
2727251881Speter      if (! target->has_text_changes
2728251881Speter          && target->kind_on_disk == svn_node_none
2729251881Speter          && ! target->added)
2730251881Speter        {
2731251881Speter          if (! dry_run)
2732251881Speter            {
2733251881Speter              SVN_ERR(svn_io_file_create(target->local_abspath, "",
2734251881Speter                                         scratch_pool));
2735251881Speter              SVN_ERR(svn_wc_add_from_disk2(ctx->wc_ctx, target->local_abspath,
2736251881Speter                                            NULL /*props*/,
2737251881Speter                                            /* suppress notification */
2738251881Speter                                            NULL, NULL,
2739251881Speter                                            iterpool));
2740251881Speter            }
2741251881Speter          target->added = TRUE;
2742251881Speter        }
2743251881Speter
2744251881Speter      /* Attempt to set the property, and reject all hunks if this
2745251881Speter         fails.  If the property had a non-empty value, but now has
2746251881Speter         an empty one, we'll just delete the property altogether.  */
2747251881Speter      if (prop_target->value && prop_target->value->len
2748251881Speter          && prop_target->patched_value && !prop_target->patched_value->len)
2749251881Speter        prop_val = NULL;
2750251881Speter      else
2751251881Speter        prop_val = svn_stringbuf__morph_into_string(prop_target->patched_value);
2752251881Speter
2753251881Speter      if (dry_run)
2754251881Speter        {
2755251881Speter          const svn_string_t *canon_propval;
2756251881Speter
2757251881Speter          err = svn_wc_canonicalize_svn_prop(&canon_propval,
2758251881Speter                                             prop_target->name,
2759251881Speter                                             prop_val, target->local_abspath,
2760251881Speter                                             target->db_kind,
2761251881Speter                                             TRUE, /* ### Skipping checks */
2762251881Speter                                             NULL, NULL,
2763251881Speter                                             iterpool);
2764251881Speter        }
2765251881Speter      else
2766251881Speter        {
2767251881Speter          err = svn_wc_prop_set4(ctx->wc_ctx, target->local_abspath,
2768251881Speter                                 prop_target->name, prop_val, svn_depth_empty,
2769251881Speter                                 TRUE /* skip_checks */,
2770251881Speter                                 NULL /* changelist_filter */,
2771251881Speter                                 NULL, NULL /* cancellation */,
2772251881Speter                                 NULL, NULL /* notification */,
2773251881Speter                                 iterpool);
2774251881Speter        }
2775251881Speter
2776251881Speter      if (err)
2777251881Speter        {
2778251881Speter          /* ### The errors which svn_wc_canonicalize_svn_prop() will
2779251881Speter           * ### return aren't documented. */
2780251881Speter          if (err->apr_err == SVN_ERR_ILLEGAL_TARGET ||
2781251881Speter              err->apr_err == SVN_ERR_NODE_UNEXPECTED_KIND ||
2782251881Speter              err->apr_err == SVN_ERR_IO_UNKNOWN_EOL ||
2783251881Speter              err->apr_err == SVN_ERR_BAD_MIME_TYPE ||
2784251881Speter              err->apr_err == SVN_ERR_CLIENT_INVALID_EXTERNALS_DESCRIPTION)
2785251881Speter            {
2786251881Speter              int i;
2787251881Speter
2788251881Speter              svn_error_clear(err);
2789251881Speter
2790251881Speter              for (i = 0; i < prop_target->content->hunks->nelts; i++)
2791251881Speter                {
2792251881Speter                  hunk_info_t *hunk_info;
2793251881Speter
2794251881Speter                  hunk_info = APR_ARRAY_IDX(prop_target->content->hunks,
2795251881Speter                                            i, hunk_info_t *);
2796251881Speter                  hunk_info->rejected = TRUE;
2797251881Speter                  SVN_ERR(reject_hunk(target, prop_target->content,
2798251881Speter                                      hunk_info->hunk, prop_target->name,
2799251881Speter                                      iterpool));
2800251881Speter                }
2801251881Speter            }
2802251881Speter          else
2803251881Speter            return svn_error_trace(err);
2804251881Speter        }
2805251881Speter
2806251881Speter    }
2807251881Speter
2808251881Speter  svn_pool_destroy(iterpool);
2809251881Speter
2810251881Speter  return SVN_NO_ERROR;
2811251881Speter}
2812251881Speter
2813251881Speter/* Baton for can_delete_callback */
2814251881Speterstruct can_delete_baton_t
2815251881Speter{
2816251881Speter  svn_boolean_t must_keep;
2817251881Speter  const apr_array_header_t *targets_info;
2818251881Speter  const char *local_abspath;
2819251881Speter};
2820251881Speter
2821251881Speter/* Implements svn_wc_status_func4_t. */
2822251881Speterstatic svn_error_t *
2823251881Spetercan_delete_callback(void *baton,
2824251881Speter                    const char *abspath,
2825251881Speter                    const svn_wc_status3_t *status,
2826251881Speter                    apr_pool_t *pool)
2827251881Speter{
2828251881Speter  struct can_delete_baton_t *cb = baton;
2829251881Speter  int i;
2830251881Speter
2831251881Speter  switch(status->node_status)
2832251881Speter    {
2833251881Speter      case svn_wc_status_none:
2834251881Speter      case svn_wc_status_deleted:
2835251881Speter        return SVN_NO_ERROR;
2836251881Speter
2837251881Speter      default:
2838251881Speter        if (! strcmp(cb->local_abspath, abspath))
2839251881Speter          return SVN_NO_ERROR; /* Only interested in descendants */
2840251881Speter
2841251881Speter        for (i = 0; i < cb->targets_info->nelts; i++)
2842251881Speter          {
2843251881Speter            const patch_target_info_t *target_info =
2844251881Speter               APR_ARRAY_IDX(cb->targets_info, i, const patch_target_info_t *);
2845251881Speter
2846251881Speter            if (! strcmp(target_info->local_abspath, abspath))
2847251881Speter              {
2848251881Speter                if (target_info->deleted)
2849251881Speter                  return SVN_NO_ERROR;
2850251881Speter
2851251881Speter                break; /* Cease invocation; must keep */
2852251881Speter              }
2853251881Speter          }
2854251881Speter
2855251881Speter        cb->must_keep = TRUE;
2856251881Speter
2857251881Speter        return svn_error_create(SVN_ERR_CEASE_INVOCATION, NULL, NULL);
2858251881Speter    }
2859251881Speter}
2860251881Speter
2861251881Speterstatic svn_error_t *
2862251881Spetercheck_ancestor_delete(const char *deleted_target,
2863251881Speter                      apr_array_header_t *targets_info,
2864251881Speter                      const char *apply_root,
2865251881Speter                      svn_boolean_t dry_run,
2866251881Speter                      svn_client_ctx_t *ctx,
2867251881Speter                      apr_pool_t *result_pool,
2868251881Speter                      apr_pool_t *scratch_pool)
2869251881Speter{
2870251881Speter  struct can_delete_baton_t cb;
2871251881Speter  svn_error_t *err;
2872251881Speter  apr_pool_t *iterpool = svn_pool_create(scratch_pool);
2873251881Speter
2874251881Speter  const char *dir_abspath = svn_dirent_dirname(deleted_target, scratch_pool);
2875251881Speter
2876251881Speter  while (svn_dirent_is_child(apply_root, dir_abspath, iterpool))
2877251881Speter    {
2878251881Speter      svn_pool_clear(iterpool);
2879251881Speter
2880251881Speter      cb.local_abspath = dir_abspath;
2881251881Speter      cb.must_keep = FALSE;
2882251881Speter      cb.targets_info = targets_info;
2883251881Speter
2884251881Speter      err = svn_wc_walk_status(ctx->wc_ctx, dir_abspath, svn_depth_infinity,
2885251881Speter                               TRUE, FALSE, FALSE, NULL,
2886251881Speter                               can_delete_callback, &cb,
2887251881Speter                               ctx->cancel_func, ctx->cancel_baton,
2888251881Speter                               iterpool);
2889251881Speter
2890251881Speter      if (err)
2891251881Speter        {
2892251881Speter          if (err->apr_err != SVN_ERR_CEASE_INVOCATION)
2893251881Speter            return svn_error_trace(err);
2894251881Speter
2895251881Speter          svn_error_clear(err);
2896251881Speter        }
2897251881Speter
2898251881Speter      if (cb.must_keep)
2899251881Speter      {
2900251881Speter        break;
2901251881Speter      }
2902251881Speter
2903251881Speter      if (! dry_run)
2904251881Speter        {
2905251881Speter          SVN_ERR(svn_wc_delete4(ctx->wc_ctx, dir_abspath, FALSE, FALSE,
2906251881Speter                                 ctx->cancel_func, ctx->cancel_baton,
2907251881Speter                                 NULL, NULL,
2908251881Speter                                 scratch_pool));
2909251881Speter        }
2910251881Speter
2911251881Speter      {
2912251881Speter        patch_target_info_t *pti = apr_pcalloc(result_pool, sizeof(*pti));
2913251881Speter
2914251881Speter        pti->local_abspath = apr_pstrdup(result_pool, dir_abspath);
2915251881Speter        pti->deleted = TRUE;
2916251881Speter
2917251881Speter        APR_ARRAY_PUSH(targets_info, patch_target_info_t *) = pti;
2918251881Speter      }
2919251881Speter
2920251881Speter
2921251881Speter      if (ctx->notify_func2)
2922251881Speter        {
2923251881Speter          svn_wc_notify_t *notify;
2924251881Speter
2925251881Speter          notify = svn_wc_create_notify(dir_abspath, svn_wc_notify_delete,
2926251881Speter                                    iterpool);
2927251881Speter          notify->kind = svn_node_dir;
2928251881Speter
2929251881Speter          ctx->notify_func2(ctx->notify_baton2, notify, iterpool);
2930251881Speter        }
2931251881Speter
2932251881Speter      /* And check if we must also delete the parent */
2933251881Speter      dir_abspath = svn_dirent_dirname(dir_abspath, scratch_pool);
2934251881Speter    }
2935251881Speter
2936251881Speter  svn_pool_destroy(iterpool);
2937251881Speter
2938251881Speter  return SVN_NO_ERROR;
2939251881Speter}
2940251881Speter
2941251881Speter/* This function is the main entry point into the patch code. */
2942251881Speterstatic svn_error_t *
2943251881Speterapply_patches(/* The path to the patch file. */
2944251881Speter              const char *patch_abspath,
2945251881Speter              /* The abspath to the working copy the patch should be applied to. */
2946251881Speter              const char *abs_wc_path,
2947251881Speter              /* Indicates whether we're doing a dry run. */
2948251881Speter              svn_boolean_t dry_run,
2949251881Speter              /* Number of leading components to strip from patch target paths. */
2950251881Speter              int strip_count,
2951251881Speter              /* Whether to apply the patch in reverse. */
2952251881Speter              svn_boolean_t reverse,
2953251881Speter              /* Whether to ignore whitespace when matching context lines. */
2954251881Speter              svn_boolean_t ignore_whitespace,
2955251881Speter              /* As in svn_client_patch(). */
2956251881Speter              svn_boolean_t remove_tempfiles,
2957251881Speter              /* As in svn_client_patch(). */
2958251881Speter              svn_client_patch_func_t patch_func,
2959251881Speter              void *patch_baton,
2960251881Speter              /* The client context. */
2961251881Speter              svn_client_ctx_t *ctx,
2962251881Speter              apr_pool_t *scratch_pool)
2963251881Speter{
2964251881Speter  svn_patch_t *patch;
2965251881Speter  apr_pool_t *iterpool;
2966251881Speter  svn_patch_file_t *patch_file;
2967251881Speter  apr_array_header_t *targets_info;
2968251881Speter
2969251881Speter  /* Try to open the patch file. */
2970251881Speter  SVN_ERR(svn_diff_open_patch_file(&patch_file, patch_abspath, scratch_pool));
2971251881Speter
2972251881Speter  /* Apply patches. */
2973251881Speter  targets_info = apr_array_make(scratch_pool, 0,
2974251881Speter                                sizeof(patch_target_info_t *));
2975251881Speter  iterpool = svn_pool_create(scratch_pool);
2976251881Speter  do
2977251881Speter    {
2978251881Speter      svn_pool_clear(iterpool);
2979251881Speter
2980251881Speter      if (ctx->cancel_func)
2981251881Speter        SVN_ERR(ctx->cancel_func(ctx->cancel_baton));
2982251881Speter
2983251881Speter      SVN_ERR(svn_diff_parse_next_patch(&patch, patch_file,
2984251881Speter                                        reverse, ignore_whitespace,
2985251881Speter                                        iterpool, iterpool));
2986251881Speter      if (patch)
2987251881Speter        {
2988251881Speter          patch_target_t *target;
2989251881Speter
2990251881Speter          SVN_ERR(apply_one_patch(&target, patch, abs_wc_path,
2991251881Speter                                  ctx->wc_ctx, strip_count,
2992251881Speter                                  ignore_whitespace, remove_tempfiles,
2993251881Speter                                  patch_func, patch_baton,
2994251881Speter                                  ctx->cancel_func, ctx->cancel_baton,
2995251881Speter                                  iterpool, iterpool));
2996251881Speter          if (! target->filtered)
2997251881Speter            {
2998251881Speter              /* Save info we'll still need when we're done patching. */
2999251881Speter              patch_target_info_t *target_info =
3000251881Speter                apr_pcalloc(scratch_pool, sizeof(patch_target_info_t));
3001251881Speter              target_info->local_abspath = apr_pstrdup(scratch_pool,
3002251881Speter                                                       target->local_abspath);
3003251881Speter              target_info->deleted = target->deleted;
3004251881Speter
3005251881Speter              if (! target->skipped)
3006251881Speter                {
3007251881Speter                  APR_ARRAY_PUSH(targets_info,
3008251881Speter                                 patch_target_info_t *) = target_info;
3009251881Speter
3010251881Speter                  if (target->has_text_changes
3011251881Speter                      || target->added
3012251881Speter                      || target->deleted)
3013251881Speter                    SVN_ERR(install_patched_target(target, abs_wc_path,
3014251881Speter                                                   ctx, dry_run, iterpool));
3015251881Speter
3016251881Speter                  if (target->has_prop_changes && (!target->deleted))
3017251881Speter                    SVN_ERR(install_patched_prop_targets(target, ctx,
3018251881Speter                                                         dry_run, iterpool));
3019251881Speter
3020251881Speter                  SVN_ERR(write_out_rejected_hunks(target, dry_run, iterpool));
3021251881Speter                }
3022251881Speter              SVN_ERR(send_patch_notification(target, ctx, iterpool));
3023251881Speter
3024251881Speter              if (target->deleted && !target->skipped)
3025251881Speter                {
3026251881Speter                  SVN_ERR(check_ancestor_delete(target_info->local_abspath,
3027251881Speter                                                targets_info, abs_wc_path,
3028251881Speter                                                dry_run, ctx,
3029251881Speter                                                scratch_pool, iterpool));
3030251881Speter                }
3031251881Speter            }
3032251881Speter        }
3033251881Speter    }
3034251881Speter  while (patch);
3035251881Speter
3036251881Speter  SVN_ERR(svn_diff_close_patch_file(patch_file, iterpool));
3037251881Speter  svn_pool_destroy(iterpool);
3038251881Speter
3039251881Speter  return SVN_NO_ERROR;
3040251881Speter}
3041251881Speter
3042251881Spetersvn_error_t *
3043251881Spetersvn_client_patch(const char *patch_abspath,
3044251881Speter                 const char *wc_dir_abspath,
3045251881Speter                 svn_boolean_t dry_run,
3046251881Speter                 int strip_count,
3047251881Speter                 svn_boolean_t reverse,
3048251881Speter                 svn_boolean_t ignore_whitespace,
3049251881Speter                 svn_boolean_t remove_tempfiles,
3050251881Speter                 svn_client_patch_func_t patch_func,
3051251881Speter                 void *patch_baton,
3052251881Speter                 svn_client_ctx_t *ctx,
3053251881Speter                 apr_pool_t *scratch_pool)
3054251881Speter{
3055251881Speter  svn_node_kind_t kind;
3056251881Speter
3057251881Speter  if (strip_count < 0)
3058251881Speter    return svn_error_create(SVN_ERR_INCORRECT_PARAMS, NULL,
3059251881Speter                            _("strip count must be positive"));
3060251881Speter
3061251881Speter  if (svn_path_is_url(wc_dir_abspath))
3062251881Speter    return svn_error_createf(SVN_ERR_ILLEGAL_TARGET, NULL,
3063251881Speter                             _("'%s' is not a local path"),
3064251881Speter                             svn_dirent_local_style(wc_dir_abspath,
3065251881Speter                                                    scratch_pool));
3066251881Speter
3067251881Speter  SVN_ERR(svn_io_check_path(patch_abspath, &kind, scratch_pool));
3068251881Speter  if (kind == svn_node_none)
3069251881Speter    return svn_error_createf(SVN_ERR_ILLEGAL_TARGET, NULL,
3070251881Speter                             _("'%s' does not exist"),
3071251881Speter                             svn_dirent_local_style(patch_abspath,
3072251881Speter                                                    scratch_pool));
3073251881Speter  if (kind != svn_node_file)
3074251881Speter    return svn_error_createf(SVN_ERR_ILLEGAL_TARGET, NULL,
3075251881Speter                             _("'%s' is not a file"),
3076251881Speter                             svn_dirent_local_style(patch_abspath,
3077251881Speter                                                    scratch_pool));
3078251881Speter
3079251881Speter  SVN_ERR(svn_io_check_path(wc_dir_abspath, &kind, scratch_pool));
3080251881Speter  if (kind == svn_node_none)
3081251881Speter    return svn_error_createf(SVN_ERR_ILLEGAL_TARGET, NULL,
3082251881Speter                             _("'%s' does not exist"),
3083251881Speter                             svn_dirent_local_style(wc_dir_abspath,
3084251881Speter                                                    scratch_pool));
3085251881Speter  if (kind != svn_node_dir)
3086251881Speter    return svn_error_createf(SVN_ERR_ILLEGAL_TARGET, NULL,
3087251881Speter                             _("'%s' is not a directory"),
3088251881Speter                             svn_dirent_local_style(wc_dir_abspath,
3089251881Speter                                                    scratch_pool));
3090251881Speter
3091251881Speter  SVN_WC__CALL_WITH_WRITE_LOCK(
3092251881Speter    apply_patches(patch_abspath, wc_dir_abspath, dry_run, strip_count,
3093251881Speter                  reverse, ignore_whitespace, remove_tempfiles,
3094251881Speter                  patch_func, patch_baton, ctx, scratch_pool),
3095251881Speter    ctx->wc_ctx, wc_dir_abspath, FALSE /* lock_anchor */, scratch_pool);
3096251881Speter  return SVN_NO_ERROR;
3097251881Speter}
3098