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"
51299742Sdim#include "private/svn_sorts_private.h"
52251881Speter
53251881Spetertypedef struct hunk_info_t {
54251881Speter  /* The hunk. */
55251881Speter  svn_diff_hunk_t *hunk;
56251881Speter
57251881Speter  /* The line where the hunk matched in the target file. */
58251881Speter  svn_linenum_t matched_line;
59251881Speter
60251881Speter  /* Whether this hunk has been rejected. */
61251881Speter  svn_boolean_t rejected;
62251881Speter
63251881Speter  /* Whether this hunk has already been applied (either manually
64251881Speter   * or by an earlier run of patch). */
65251881Speter  svn_boolean_t already_applied;
66251881Speter
67251881Speter  /* The fuzz factor used when matching this hunk, i.e. how many
68251881Speter   * lines of leading and trailing context to ignore during matching. */
69251881Speter  svn_linenum_t fuzz;
70251881Speter} hunk_info_t;
71251881Speter
72251881Speter/* A struct carrying information related to the patched and unpatched
73251881Speter * content of a target, be it a property or the text of a file. */
74251881Spetertypedef struct target_content_t {
75251881Speter  /* Indicates whether unpatched content existed prior to patching. */
76251881Speter  svn_boolean_t existed;
77251881Speter
78251881Speter  /* The line last read from the unpatched content. */
79251881Speter  svn_linenum_t current_line;
80251881Speter
81251881Speter  /* The EOL-style of the unpatched content. Either 'none', 'fixed',
82251881Speter   * or 'native'. See the documentation of svn_subst_eol_style_t. */
83251881Speter  svn_subst_eol_style_t eol_style;
84251881Speter
85251881Speter  /* If the EOL_STYLE above is not 'none', this is the EOL string
86251881Speter   * corresponding to the EOL-style. Else, it is the EOL string the
87251881Speter   * last line read from the target file was using. */
88251881Speter  const char *eol_str;
89251881Speter
90251881Speter  /* An array containing apr_off_t offsets marking the beginning of
91251881Speter   * each line in the unpatched content. */
92251881Speter  apr_array_header_t *lines;
93251881Speter
94251881Speter  /* An array containing hunk_info_t structures for hunks already matched. */
95251881Speter  apr_array_header_t *hunks;
96251881Speter
97251881Speter  /* True if end-of-file was reached while reading from the unpatched
98251881Speter   * content. */
99251881Speter  svn_boolean_t eof;
100251881Speter
101251881Speter  /* The keywords of the target. They will be contracted when reading
102251881Speter   * unpatched content and expanded when writing patched content.
103251881Speter   * When patching properties this hash is always empty. */
104251881Speter  apr_hash_t *keywords;
105251881Speter
106251881Speter  /* A callback, with an associated baton, to read a line of unpatched
107251881Speter   * content. */
108251881Speter  svn_error_t *(*readline)(void *baton, svn_stringbuf_t **line,
109251881Speter                           const char **eol_str, svn_boolean_t *eof,
110251881Speter                           apr_pool_t *result_pool, apr_pool_t *scratch_pool);
111251881Speter  void *read_baton;
112251881Speter
113251881Speter  /* A callback to get the current byte offset within the unpatched
114251881Speter   * content. Uses the read baton. */
115251881Speter  svn_error_t * (*tell)(void *baton, apr_off_t *offset,
116251881Speter                        apr_pool_t *scratch_pool);
117251881Speter
118251881Speter  /* A callback to seek to an offset within the unpatched content.
119251881Speter   * Uses the read baton. */
120251881Speter  svn_error_t * (*seek)(void *baton, apr_off_t offset,
121251881Speter                        apr_pool_t *scratch_pool);
122251881Speter
123251881Speter  /* A callback to write data to the patched content, with an
124251881Speter   * associated baton. */
125251881Speter  svn_error_t * (*write)(void *baton, const char *buf, apr_size_t len,
126251881Speter                         apr_pool_t *scratch_pool);
127251881Speter  void *write_baton;
128251881Speter
129251881Speter} target_content_t;
130251881Speter
131251881Spetertypedef struct prop_patch_target_t {
132251881Speter
133251881Speter  /* The name of the property */
134251881Speter  const char *name;
135251881Speter
136251881Speter  /* The property value. This is NULL in case the property did not exist
137251881Speter   * prior to patch application (see also CONTENT->existed).
138251881Speter   * Note that the patch implementation does not support binary properties,
139251881Speter   * so this string is not expected to contain embedded NUL characters. */
140251881Speter  const svn_string_t *value;
141251881Speter
142251881Speter  /* The patched property value.
143251881Speter   * This is equivalent to the target, except that in appropriate
144251881Speter   * places it contains the modified text as it appears in the patch file. */
145251881Speter  svn_stringbuf_t *patched_value;
146251881Speter
147251881Speter  /* All information that is specific to the content of the property. */
148251881Speter  target_content_t *content;
149251881Speter
150251881Speter  /* Represents the operation performed on the property. It can be added,
151251881Speter   * deleted or modified.
152251881Speter   * ### Should we use flags instead since we're not using all enum values? */
153251881Speter  svn_diff_operation_kind_t operation;
154251881Speter
155251881Speter  /* ### Here we'll add flags telling if the prop was added, deleted,
156251881Speter   * ### had_rejects, had_local_mods prior to patching and so on. */
157251881Speter} prop_patch_target_t;
158251881Speter
159251881Spetertypedef struct patch_target_t {
160251881Speter  /* The target path as it appeared in the patch file,
161251881Speter   * but in canonicalised form. */
162251881Speter  const char *canon_path_from_patchfile;
163251881Speter
164251881Speter  /* The target path, relative to the working copy directory the
165251881Speter   * patch is being applied to. A patch strip count applies to this
166251881Speter   * and only this path. This is never NULL. */
167251881Speter  const char *local_relpath;
168251881Speter
169251881Speter  /* The absolute path of the target on the filesystem.
170251881Speter   * Any symlinks the path from the patch file may contain are resolved.
171251881Speter   * Is not always known, so it may be NULL. */
172251881Speter  const char *local_abspath;
173251881Speter
174251881Speter  /* The target file, read-only. This is NULL in case the target
175251881Speter   * file did not exist prior to patch application (see also
176251881Speter   * CONTENT->existed). */
177251881Speter  apr_file_t *file;
178251881Speter
179251881Speter  /* The target file is a symlink */
180251881Speter  svn_boolean_t is_symlink;
181251881Speter
182251881Speter  /* The patched file.
183251881Speter   * This is equivalent to the target, except that in appropriate
184251881Speter   * places it contains the modified text as it appears in the patch file.
185251881Speter   * The data in this file is written in repository-normal form.
186251881Speter   * EOL transformation and keyword contraction is performed when the
187251881Speter   * patched result is installed in the working copy. */
188251881Speter  apr_file_t *patched_file;
189251881Speter
190251881Speter  /* Path to the patched file. */
191251881Speter  const char *patched_path;
192251881Speter
193251881Speter  /* Hunks that are rejected will be written to this file. */
194251881Speter  apr_file_t *reject_file;
195251881Speter
196251881Speter  /* Path to the reject file. */
197251881Speter  const char *reject_path;
198251881Speter
199251881Speter  /* The node kind of the target as found in WC-DB prior
200251881Speter   * to patch application. */
201251881Speter  svn_node_kind_t db_kind;
202251881Speter
203251881Speter  /* The target's kind on disk prior to patch application. */
204251881Speter  svn_node_kind_t kind_on_disk;
205251881Speter
206251881Speter  /* True if the target was locally deleted prior to patching. */
207251881Speter  svn_boolean_t locally_deleted;
208251881Speter
209251881Speter  /* True if the target had to be skipped for some reason. */
210251881Speter  svn_boolean_t skipped;
211251881Speter
212251881Speter  /* True if at least one hunk was rejected. */
213251881Speter  svn_boolean_t had_rejects;
214251881Speter
215251881Speter  /* True if at least one property hunk was rejected. */
216251881Speter  svn_boolean_t had_prop_rejects;
217251881Speter
218251881Speter  /* True if the target file had local modifications before the
219251881Speter   * patch was applied to it. */
220251881Speter  svn_boolean_t local_mods;
221251881Speter
222251881Speter  /* True if the target was added by the patch, which means that it did
223251881Speter   * not exist on disk before patching and has content after patching. */
224251881Speter  svn_boolean_t added;
225251881Speter
226251881Speter  /* True if the target ended up being deleted by the patch. */
227251881Speter  svn_boolean_t deleted;
228251881Speter
229251881Speter  /* True if the target ended up being replaced by the patch
230251881Speter   * (i.e. a new file was added on top locally deleted node). */
231251881Speter  svn_boolean_t replaced;
232251881Speter
233299742Sdim  /* Set if the target is supposed to be moved by the patch.
234299742Sdim   * This applies to --git diffs which carry "rename from/to" headers. */
235299742Sdim   const char *move_target_abspath;
236299742Sdim
237251881Speter  /* True if the target has the executable bit set. */
238251881Speter  svn_boolean_t executable;
239251881Speter
240251881Speter  /* True if the patch changed the text of the target. */
241251881Speter  svn_boolean_t has_text_changes;
242251881Speter
243251881Speter  /* True if the patch changed any of the properties of the target. */
244251881Speter  svn_boolean_t has_prop_changes;
245251881Speter
246251881Speter  /* True if the patch contained a svn:special property. */
247251881Speter  svn_boolean_t is_special;
248251881Speter
249251881Speter  /* All the information that is specific to the content of the target. */
250251881Speter  target_content_t *content;
251251881Speter
252251881Speter  /* A hash table of prop_patch_target_t objects keyed by property names. */
253251881Speter  apr_hash_t *prop_targets;
254251881Speter
255251881Speter} patch_target_t;
256251881Speter
257251881Speter
258251881Speter/* A smaller struct containing a subset of patch_target_t.
259251881Speter * Carries the minimal amount of information we still need for a
260251881Speter * target after we're done patching it so we can free other resources. */
261251881Spetertypedef struct patch_target_info_t {
262251881Speter  const char *local_abspath;
263251881Speter  svn_boolean_t deleted;
264251881Speter} patch_target_info_t;
265251881Speter
266251881Speter
267251881Speter/* Strip STRIP_COUNT components from the front of PATH, returning
268251881Speter * the result in *RESULT, allocated in RESULT_POOL.
269251881Speter * Do temporary allocations in SCRATCH_POOL. */
270251881Speterstatic svn_error_t *
271251881Speterstrip_path(const char **result, const char *path, int strip_count,
272251881Speter           apr_pool_t *result_pool, apr_pool_t *scratch_pool)
273251881Speter{
274251881Speter  int i;
275251881Speter  apr_array_header_t *components;
276251881Speter  apr_array_header_t *stripped;
277251881Speter
278251881Speter  components = svn_path_decompose(path, scratch_pool);
279251881Speter  if (strip_count > components->nelts)
280251881Speter    return svn_error_createf(SVN_ERR_CLIENT_PATCH_BAD_STRIP_COUNT, NULL,
281251881Speter                             _("Cannot strip %u components from '%s'"),
282251881Speter                             strip_count,
283251881Speter                             svn_dirent_local_style(path, scratch_pool));
284251881Speter
285251881Speter  stripped = apr_array_make(scratch_pool, components->nelts - strip_count,
286251881Speter                            sizeof(const char *));
287251881Speter  for (i = strip_count; i < components->nelts; i++)
288251881Speter    {
289251881Speter      const char *component;
290251881Speter
291251881Speter      component = APR_ARRAY_IDX(components, i, const char *);
292251881Speter      APR_ARRAY_PUSH(stripped, const char *) = component;
293251881Speter    }
294251881Speter
295251881Speter  *result = svn_path_compose(stripped, result_pool);
296251881Speter
297251881Speter  return SVN_NO_ERROR;
298251881Speter}
299251881Speter
300251881Speter/* Obtain KEYWORDS, EOL_STYLE and EOL_STR for LOCAL_ABSPATH.
301251881Speter * WC_CTX is a context for the working copy the patch is applied to.
302251881Speter * Use RESULT_POOL for allocations of fields in TARGET.
303251881Speter * Use SCRATCH_POOL for all other allocations. */
304251881Speterstatic svn_error_t *
305251881Speterobtain_eol_and_keywords_for_file(apr_hash_t **keywords,
306251881Speter                                 svn_subst_eol_style_t *eol_style,
307251881Speter                                 const char **eol_str,
308251881Speter                                 svn_wc_context_t *wc_ctx,
309251881Speter                                 const char *local_abspath,
310251881Speter                                 apr_pool_t *result_pool,
311251881Speter                                 apr_pool_t *scratch_pool)
312251881Speter{
313251881Speter  apr_hash_t *props;
314251881Speter  svn_string_t *keywords_val, *eol_style_val;
315251881Speter
316251881Speter  SVN_ERR(svn_wc_prop_list2(&props, wc_ctx, local_abspath,
317251881Speter                            scratch_pool, scratch_pool));
318251881Speter  keywords_val = svn_hash_gets(props, SVN_PROP_KEYWORDS);
319251881Speter  if (keywords_val)
320251881Speter    {
321251881Speter      svn_revnum_t changed_rev;
322251881Speter      apr_time_t changed_date;
323251881Speter      const char *rev_str;
324251881Speter      const char *author;
325251881Speter      const char *url;
326299742Sdim      const char *repos_root_url;
327299742Sdim      const char *repos_relpath;
328251881Speter
329251881Speter      SVN_ERR(svn_wc__node_get_changed_info(&changed_rev,
330251881Speter                                            &changed_date,
331251881Speter                                            &author, wc_ctx,
332251881Speter                                            local_abspath,
333251881Speter                                            scratch_pool,
334251881Speter                                            scratch_pool));
335251881Speter      rev_str = apr_psprintf(scratch_pool, "%ld", changed_rev);
336299742Sdim      SVN_ERR(svn_wc__node_get_repos_info(NULL, &repos_relpath, &repos_root_url,
337299742Sdim                                          NULL,
338251881Speter                                          wc_ctx, local_abspath,
339251881Speter                                          scratch_pool, scratch_pool));
340299742Sdim      url = svn_path_url_add_component2(repos_root_url, repos_relpath,
341299742Sdim                                        scratch_pool);
342299742Sdim
343251881Speter      SVN_ERR(svn_subst_build_keywords3(keywords,
344251881Speter                                        keywords_val->data,
345299742Sdim                                        rev_str, url, repos_root_url,
346299742Sdim                                        changed_date,
347251881Speter                                        author, result_pool));
348251881Speter    }
349251881Speter
350251881Speter  eol_style_val = svn_hash_gets(props, SVN_PROP_EOL_STYLE);
351251881Speter  if (eol_style_val)
352251881Speter    {
353251881Speter      svn_subst_eol_style_from_value(eol_style,
354251881Speter                                     eol_str,
355251881Speter                                     eol_style_val->data);
356251881Speter    }
357251881Speter
358251881Speter  return SVN_NO_ERROR;
359251881Speter}
360251881Speter
361251881Speter/* Resolve the exact path for a patch TARGET at path PATH_FROM_PATCHFILE,
362251881Speter * which is the path of the target as it appeared in the patch file.
363251881Speter * Put a canonicalized version of PATH_FROM_PATCHFILE into
364251881Speter * TARGET->CANON_PATH_FROM_PATCHFILE.
365251881Speter * WC_CTX is a context for the working copy the patch is applied to.
366251881Speter * If possible, determine TARGET->WC_PATH, TARGET->ABS_PATH, TARGET->KIND,
367251881Speter * TARGET->ADDED, and TARGET->PARENT_DIR_EXISTS.
368251881Speter * Indicate in TARGET->SKIPPED whether the target should be skipped.
369251881Speter * STRIP_COUNT specifies the number of leading path components
370251881Speter * which should be stripped from target paths in the patch.
371251881Speter * PROP_CHANGES_ONLY specifies whether the target path is allowed to have
372251881Speter * only property changes, and no content changes (in which case the target
373251881Speter * must be a directory).
374251881Speter * Use RESULT_POOL for allocations of fields in TARGET.
375251881Speter * Use SCRATCH_POOL for all other allocations. */
376251881Speterstatic svn_error_t *
377251881Speterresolve_target_path(patch_target_t *target,
378251881Speter                    const char *path_from_patchfile,
379251881Speter                    const char *wcroot_abspath,
380251881Speter                    int strip_count,
381251881Speter                    svn_boolean_t prop_changes_only,
382251881Speter                    svn_wc_context_t *wc_ctx,
383251881Speter                    apr_pool_t *result_pool,
384251881Speter                    apr_pool_t *scratch_pool)
385251881Speter{
386251881Speter  const char *stripped_path;
387251881Speter  svn_wc_status3_t *status;
388251881Speter  svn_error_t *err;
389251881Speter  svn_boolean_t under_root;
390251881Speter
391251881Speter  target->canon_path_from_patchfile = svn_dirent_internal_style(
392251881Speter                                        path_from_patchfile, result_pool);
393251881Speter
394251881Speter  /* We allow properties to be set on the wc root dir. */
395251881Speter  if (! prop_changes_only && target->canon_path_from_patchfile[0] == '\0')
396251881Speter    {
397251881Speter      /* An empty patch target path? What gives? Skip this. */
398251881Speter      target->skipped = TRUE;
399251881Speter      target->local_abspath = NULL;
400251881Speter      target->local_relpath = "";
401251881Speter      return SVN_NO_ERROR;
402251881Speter    }
403251881Speter
404251881Speter  if (strip_count > 0)
405251881Speter    SVN_ERR(strip_path(&stripped_path, target->canon_path_from_patchfile,
406251881Speter                       strip_count, result_pool, scratch_pool));
407251881Speter  else
408251881Speter    stripped_path = target->canon_path_from_patchfile;
409251881Speter
410251881Speter  if (svn_dirent_is_absolute(stripped_path))
411251881Speter    {
412251881Speter      target->local_relpath = svn_dirent_is_child(wcroot_abspath,
413251881Speter                                                  stripped_path,
414251881Speter                                                  result_pool);
415251881Speter
416251881Speter      if (! target->local_relpath)
417251881Speter        {
418251881Speter          /* The target path is either outside of the working copy
419251881Speter           * or it is the working copy itself. Skip it. */
420251881Speter          target->skipped = TRUE;
421251881Speter          target->local_abspath = NULL;
422251881Speter          target->local_relpath = stripped_path;
423251881Speter          return SVN_NO_ERROR;
424251881Speter        }
425251881Speter    }
426251881Speter  else
427251881Speter    {
428251881Speter      target->local_relpath = stripped_path;
429251881Speter    }
430251881Speter
431251881Speter  /* Make sure the path is secure to use. We want the target to be inside
432251881Speter   * of the working copy and not be fooled by symlinks it might contain. */
433251881Speter  SVN_ERR(svn_dirent_is_under_root(&under_root,
434251881Speter                                   &target->local_abspath, wcroot_abspath,
435251881Speter                                   target->local_relpath, result_pool));
436251881Speter
437251881Speter  if (! under_root)
438251881Speter    {
439251881Speter      /* The target path is outside of the working copy. Skip it. */
440251881Speter      target->skipped = TRUE;
441251881Speter      target->local_abspath = NULL;
442251881Speter      return SVN_NO_ERROR;
443251881Speter    }
444251881Speter
445251881Speter  /* Skip things we should not be messing with. */
446251881Speter  err = svn_wc_status3(&status, wc_ctx, target->local_abspath,
447251881Speter                       result_pool, scratch_pool);
448251881Speter  if (err)
449251881Speter    {
450251881Speter      if (err->apr_err != SVN_ERR_WC_PATH_NOT_FOUND)
451251881Speter        return svn_error_trace(err);
452251881Speter
453251881Speter      svn_error_clear(err);
454251881Speter
455251881Speter      target->locally_deleted = TRUE;
456251881Speter      target->db_kind = svn_node_none;
457251881Speter      status = NULL;
458251881Speter    }
459251881Speter  else if (status->node_status == svn_wc_status_ignored ||
460251881Speter           status->node_status == svn_wc_status_unversioned ||
461251881Speter           status->node_status == svn_wc_status_missing ||
462251881Speter           status->node_status == svn_wc_status_obstructed ||
463251881Speter           status->conflicted)
464251881Speter    {
465251881Speter      target->skipped = TRUE;
466251881Speter      return SVN_NO_ERROR;
467251881Speter    }
468251881Speter  else if (status->node_status == svn_wc_status_deleted)
469251881Speter    {
470251881Speter      target->locally_deleted = TRUE;
471251881Speter    }
472251881Speter
473251881Speter  if (status && (status->kind != svn_node_unknown))
474251881Speter    target->db_kind = status->kind;
475251881Speter  else
476251881Speter    target->db_kind = svn_node_none;
477251881Speter
478251881Speter  SVN_ERR(svn_io_check_special_path(target->local_abspath,
479251881Speter                                    &target->kind_on_disk, &target->is_symlink,
480251881Speter                                    scratch_pool));
481251881Speter
482251881Speter  if (target->locally_deleted)
483251881Speter    {
484251881Speter      const char *moved_to_abspath;
485251881Speter
486251881Speter      SVN_ERR(svn_wc__node_was_moved_away(&moved_to_abspath, NULL,
487251881Speter                                          wc_ctx, target->local_abspath,
488251881Speter                                          result_pool, scratch_pool));
489299742Sdim      /* ### BUG: moved_to_abspath contains the target where the op-root was
490299742Sdim         ### moved to... not the target itself! */
491251881Speter      if (moved_to_abspath)
492251881Speter        {
493251881Speter          target->local_abspath = moved_to_abspath;
494251881Speter          target->local_relpath = svn_dirent_skip_ancestor(wcroot_abspath,
495251881Speter                                                          moved_to_abspath);
496251881Speter          SVN_ERR_ASSERT(target->local_relpath &&
497251881Speter                         target->local_relpath[0] != '\0');
498251881Speter
499251881Speter          /* As far as we are concerned this target is not locally deleted. */
500251881Speter          target->locally_deleted = FALSE;
501251881Speter
502251881Speter          SVN_ERR(svn_io_check_special_path(target->local_abspath,
503251881Speter                                            &target->kind_on_disk,
504251881Speter                                            &target->is_symlink,
505251881Speter                                            scratch_pool));
506251881Speter        }
507251881Speter      else if (target->kind_on_disk != svn_node_none)
508251881Speter        {
509251881Speter          target->skipped = TRUE;
510251881Speter          return SVN_NO_ERROR;
511251881Speter        }
512251881Speter    }
513251881Speter
514251881Speter  return SVN_NO_ERROR;
515251881Speter}
516251881Speter
517251881Speter/* Baton for reading from properties. */
518251881Spetertypedef struct prop_read_baton_t {
519251881Speter  const svn_string_t *value;
520251881Speter  apr_off_t offset;
521251881Speter} prop_read_baton_t;
522251881Speter
523251881Speter/* Allocate *STRINGBUF in RESULT_POOL, and read into it one line from
524251881Speter * the unpatched property value accessed via BATON.
525251881Speter * Reading stops either after a line-terminator was found, or if
526251881Speter * the property value runs out in which case *EOF is set to TRUE.
527251881Speter * The line-terminator is not stored in *STRINGBUF.
528251881Speter *
529251881Speter * If the line is empty or could not be read, *line is set to NULL.
530251881Speter *
531251881Speter * The line-terminator is detected automatically and stored in *EOL
532251881Speter * if EOL is not NULL. If the end of the property value is reached
533251881Speter * and does not end with a newline character, and EOL is not NULL,
534251881Speter * *EOL is set to NULL.
535251881Speter *
536251881Speter * SCRATCH_POOL is used for temporary allocations.
537251881Speter */
538251881Speterstatic svn_error_t *
539251881Speterreadline_prop(void *baton, svn_stringbuf_t **line, const char **eol_str,
540251881Speter              svn_boolean_t *eof, apr_pool_t *result_pool,
541251881Speter              apr_pool_t *scratch_pool)
542251881Speter{
543251881Speter  prop_read_baton_t *b = (prop_read_baton_t *)baton;
544251881Speter  svn_stringbuf_t *str = NULL;
545251881Speter  const char *c;
546251881Speter  svn_boolean_t found_eof;
547251881Speter
548251881Speter  if ((apr_uint64_t)b->offset >= (apr_uint64_t)b->value->len)
549251881Speter    {
550251881Speter      *eol_str = NULL;
551251881Speter      *eof = TRUE;
552251881Speter      *line = NULL;
553251881Speter      return SVN_NO_ERROR;
554251881Speter    }
555251881Speter
556251881Speter  /* Read bytes into STR up to and including, but not storing,
557251881Speter   * the next EOL sequence. */
558251881Speter  *eol_str = NULL;
559251881Speter  found_eof = FALSE;
560251881Speter  do
561251881Speter    {
562251881Speter      c = b->value->data + b->offset;
563251881Speter      b->offset++;
564251881Speter
565251881Speter      if (*c == '\0')
566251881Speter        {
567251881Speter          found_eof = TRUE;
568251881Speter          break;
569251881Speter        }
570251881Speter      else if (*c == '\n')
571251881Speter        {
572251881Speter          *eol_str = "\n";
573251881Speter        }
574251881Speter      else if (*c == '\r')
575251881Speter        {
576251881Speter          *eol_str = "\r";
577251881Speter          if (*(c + 1) == '\n')
578251881Speter            {
579251881Speter              *eol_str = "\r\n";
580251881Speter              b->offset++;
581251881Speter            }
582251881Speter        }
583251881Speter      else
584251881Speter        {
585251881Speter          if (str == NULL)
586251881Speter            str = svn_stringbuf_create_ensure(80, result_pool);
587251881Speter          svn_stringbuf_appendbyte(str, *c);
588251881Speter        }
589251881Speter
590251881Speter      if (*eol_str)
591251881Speter        break;
592251881Speter    }
593251881Speter  while (c < b->value->data + b->value->len);
594251881Speter
595251881Speter  if (eof)
596251881Speter    *eof = found_eof;
597251881Speter  *line = str;
598251881Speter
599251881Speter  return SVN_NO_ERROR;
600251881Speter}
601251881Speter
602251881Speter/* Return in *OFFSET the current byte offset for reading from the
603251881Speter * unpatched property value accessed via BATON.
604251881Speter * Use SCRATCH_POOL for temporary allocations. */
605251881Speterstatic svn_error_t *
606251881Spetertell_prop(void *baton, apr_off_t *offset, apr_pool_t *scratch_pool)
607251881Speter{
608251881Speter  prop_read_baton_t *b = (prop_read_baton_t *)baton;
609251881Speter  *offset = b->offset;
610251881Speter  return SVN_NO_ERROR;
611251881Speter}
612251881Speter
613251881Speter/* Seek to the specified by OFFSET in the unpatched property value accessed
614251881Speter * via BATON. Use SCRATCH_POOL for temporary allocations. */
615251881Speterstatic svn_error_t *
616251881Speterseek_prop(void *baton, apr_off_t offset, apr_pool_t *scratch_pool)
617251881Speter{
618251881Speter  prop_read_baton_t *b = (prop_read_baton_t *)baton;
619251881Speter  b->offset = offset;
620251881Speter  return SVN_NO_ERROR;
621251881Speter}
622251881Speter
623251881Speter/* Write LEN bytes from BUF into the patched property value accessed
624251881Speter * via BATON. Use SCRATCH_POOL for temporary allocations. */
625251881Speterstatic svn_error_t *
626251881Speterwrite_prop(void *baton, const char *buf, apr_size_t len,
627251881Speter           apr_pool_t *scratch_pool)
628251881Speter{
629251881Speter  svn_stringbuf_t *patched_value = (svn_stringbuf_t *)baton;
630251881Speter  svn_stringbuf_appendbytes(patched_value, buf, len);
631251881Speter  return SVN_NO_ERROR;
632251881Speter}
633251881Speter
634251881Speter/* Initialize a PROP_TARGET structure for PROP_NAME on the patch target
635251881Speter * at LOCAL_ABSPATH. OPERATION indicates the operation performed on the
636251881Speter * property. Use working copy context WC_CTX.
637251881Speter * Allocate results in RESULT_POOL.
638251881Speter * Use SCRATCH_POOL for temporary allocations. */
639251881Speterstatic svn_error_t *
640251881Speterinit_prop_target(prop_patch_target_t **prop_target,
641251881Speter                 const char *prop_name,
642251881Speter                 svn_diff_operation_kind_t operation,
643251881Speter                 svn_wc_context_t *wc_ctx,
644251881Speter                 const char *local_abspath,
645251881Speter                 apr_pool_t *result_pool, apr_pool_t *scratch_pool)
646251881Speter{
647251881Speter  prop_patch_target_t *new_prop_target;
648251881Speter  target_content_t *content;
649251881Speter  const svn_string_t *value;
650251881Speter  svn_error_t *err;
651251881Speter  prop_read_baton_t *prop_read_baton;
652251881Speter
653251881Speter  content = apr_pcalloc(result_pool, sizeof(*content));
654251881Speter
655251881Speter  /* All other fields are FALSE or NULL due to apr_pcalloc(). */
656251881Speter  content->current_line = 1;
657251881Speter  content->eol_style = svn_subst_eol_style_none;
658251881Speter  content->lines = apr_array_make(result_pool, 0, sizeof(apr_off_t));
659251881Speter  content->hunks = apr_array_make(result_pool, 0, sizeof(hunk_info_t *));
660251881Speter  content->keywords = apr_hash_make(result_pool);
661251881Speter
662251881Speter  new_prop_target = apr_pcalloc(result_pool, sizeof(*new_prop_target));
663251881Speter  new_prop_target->name = apr_pstrdup(result_pool, prop_name);
664251881Speter  new_prop_target->operation = operation;
665251881Speter  new_prop_target->content = content;
666251881Speter
667251881Speter  err = svn_wc_prop_get2(&value, wc_ctx, local_abspath, prop_name,
668251881Speter                         result_pool, scratch_pool);
669251881Speter  if (err)
670251881Speter    {
671251881Speter      if (err->apr_err == SVN_ERR_WC_PATH_NOT_FOUND)
672251881Speter        {
673251881Speter          svn_error_clear(err);
674251881Speter          value = NULL;
675251881Speter        }
676251881Speter      else
677251881Speter        return svn_error_trace(err);
678251881Speter    }
679251881Speter  content->existed = (value != NULL);
680251881Speter  new_prop_target->value = value;
681251881Speter  new_prop_target->patched_value = svn_stringbuf_create_empty(result_pool);
682251881Speter
683251881Speter
684251881Speter  /* Wire up the read and write callbacks. */
685251881Speter  prop_read_baton = apr_pcalloc(result_pool, sizeof(*prop_read_baton));
686251881Speter  prop_read_baton->value = value;
687251881Speter  prop_read_baton->offset = 0;
688251881Speter  content->readline = readline_prop;
689251881Speter  content->tell = tell_prop;
690251881Speter  content->seek = seek_prop;
691251881Speter  content->read_baton = prop_read_baton;
692251881Speter  content->write = write_prop;
693251881Speter  content->write_baton = new_prop_target->patched_value;
694251881Speter
695251881Speter  *prop_target = new_prop_target;
696251881Speter
697251881Speter  return SVN_NO_ERROR;
698251881Speter}
699251881Speter
700251881Speter/* Allocate *STRINGBUF in RESULT_POOL, and read into it one line from
701251881Speter * the unpatched file content accessed via BATON.
702251881Speter * Reading stops either after a line-terminator was found,
703251881Speter * or if EOF is reached in which case *EOF is set to TRUE.
704251881Speter * The line-terminator is not stored in *STRINGBUF.
705251881Speter *
706251881Speter * If the line is empty or could not be read, *line is set to NULL.
707251881Speter *
708251881Speter * The line-terminator is detected automatically and stored in *EOL
709251881Speter * if EOL is not NULL. If EOF is reached and FILE does not end
710251881Speter * with a newline character, and EOL is not NULL, *EOL is set to NULL.
711251881Speter *
712251881Speter * SCRATCH_POOL is used for temporary allocations.
713251881Speter */
714251881Speterstatic svn_error_t *
715251881Speterreadline_file(void *baton, svn_stringbuf_t **line, const char **eol_str,
716251881Speter              svn_boolean_t *eof, apr_pool_t *result_pool,
717251881Speter              apr_pool_t *scratch_pool)
718251881Speter{
719251881Speter  apr_file_t *file = (apr_file_t *)baton;
720251881Speter  svn_stringbuf_t *str = NULL;
721251881Speter  apr_size_t numbytes;
722251881Speter  char c;
723251881Speter  svn_boolean_t found_eof;
724251881Speter
725251881Speter  /* Read bytes into STR up to and including, but not storing,
726251881Speter   * the next EOL sequence. */
727251881Speter  *eol_str = NULL;
728251881Speter  numbytes = 1;
729251881Speter  found_eof = FALSE;
730251881Speter  while (!found_eof)
731251881Speter    {
732251881Speter      SVN_ERR(svn_io_file_read_full2(file, &c, sizeof(c), &numbytes,
733251881Speter                                     &found_eof, scratch_pool));
734251881Speter      if (numbytes != 1)
735251881Speter        {
736251881Speter          found_eof = TRUE;
737251881Speter          break;
738251881Speter        }
739251881Speter
740251881Speter      if (c == '\n')
741251881Speter        {
742251881Speter          *eol_str = "\n";
743251881Speter        }
744251881Speter      else if (c == '\r')
745251881Speter        {
746251881Speter          *eol_str = "\r";
747251881Speter
748251881Speter          if (!found_eof)
749251881Speter            {
750251881Speter              apr_off_t pos;
751251881Speter
752251881Speter              /* Check for "\r\n" by peeking at the next byte. */
753251881Speter              pos = 0;
754251881Speter              SVN_ERR(svn_io_file_seek(file, APR_CUR, &pos, scratch_pool));
755251881Speter              SVN_ERR(svn_io_file_read_full2(file, &c, sizeof(c), &numbytes,
756251881Speter                                             &found_eof, scratch_pool));
757251881Speter              if (numbytes == 1 && c == '\n')
758251881Speter                {
759251881Speter                  *eol_str = "\r\n";
760251881Speter                }
761251881Speter              else
762251881Speter                {
763251881Speter                  /* Pretend we never peeked. */
764251881Speter                  SVN_ERR(svn_io_file_seek(file, APR_SET, &pos, scratch_pool));
765251881Speter                  found_eof = FALSE;
766251881Speter                  numbytes = 1;
767251881Speter                }
768251881Speter            }
769251881Speter        }
770251881Speter      else
771251881Speter        {
772251881Speter          if (str == NULL)
773251881Speter            str = svn_stringbuf_create_ensure(80, result_pool);
774251881Speter          svn_stringbuf_appendbyte(str, c);
775251881Speter        }
776251881Speter
777251881Speter      if (*eol_str)
778251881Speter        break;
779251881Speter    }
780251881Speter
781251881Speter  if (eof)
782251881Speter    *eof = found_eof;
783251881Speter  *line = str;
784251881Speter
785251881Speter  return SVN_NO_ERROR;
786251881Speter}
787251881Speter
788251881Speter/* Return in *OFFSET the current byte offset for reading from the
789251881Speter * unpatched file content accessed via BATON.
790251881Speter * Use SCRATCH_POOL for temporary allocations. */
791251881Speterstatic svn_error_t *
792251881Spetertell_file(void *baton, apr_off_t *offset, apr_pool_t *scratch_pool)
793251881Speter{
794251881Speter  apr_file_t *file = (apr_file_t *)baton;
795251881Speter  *offset = 0;
796251881Speter  SVN_ERR(svn_io_file_seek(file, APR_CUR, offset, scratch_pool));
797251881Speter  return SVN_NO_ERROR;
798251881Speter}
799251881Speter
800251881Speter/* Seek to the specified by OFFSET in the unpatched file content accessed
801251881Speter * via BATON. Use SCRATCH_POOL for temporary allocations. */
802251881Speterstatic svn_error_t *
803251881Speterseek_file(void *baton, apr_off_t offset, apr_pool_t *scratch_pool)
804251881Speter{
805251881Speter  apr_file_t *file = (apr_file_t *)baton;
806251881Speter  SVN_ERR(svn_io_file_seek(file, APR_SET, &offset, scratch_pool));
807251881Speter  return SVN_NO_ERROR;
808251881Speter}
809251881Speter
810251881Speter/* Write LEN bytes from BUF into the patched file content accessed
811251881Speter * via BATON. Use SCRATCH_POOL for temporary allocations. */
812251881Speterstatic svn_error_t *
813251881Speterwrite_file(void *baton, const char *buf, apr_size_t len,
814251881Speter           apr_pool_t *scratch_pool)
815251881Speter{
816251881Speter  apr_file_t *file = (apr_file_t *)baton;
817251881Speter  SVN_ERR(svn_io_file_write_full(file, buf, len, &len, scratch_pool));
818251881Speter  return SVN_NO_ERROR;
819251881Speter}
820251881Speter
821251881Speter/* Handling symbolic links:
822251881Speter *
823251881Speter * In Subversion, symlinks can be represented on disk in two distinct ways.
824251881Speter * On systems which support symlinks, a symlink is created on disk.
825251881Speter * On systems which do not support symlink, a file is created on disk
826251881Speter * which contains the "normal form" of the symlink, which looks like:
827251881Speter *   link TARGET
828251881Speter * where TARGET is the file the symlink points to.
829251881Speter *
830251881Speter * When reading symlinks (i.e. the link itself, not the file the symlink
831251881Speter * is pointing to) through the svn_subst_create_specialfile() function
832251881Speter * into a buffer, the buffer always contains the "normal form" of the symlink.
833251881Speter * Due to this representation symlinks always contain a single line of text.
834251881Speter *
835251881Speter * The functions below are needed to deal with the case where a patch
836251881Speter * wants to change the TARGET that a symlink points to.
837251881Speter */
838251881Speter
839251881Speter/* Baton for the (readline|tell|seek|write)_symlink functions. */
840251881Speterstruct symlink_baton_t
841251881Speter{
842251881Speter  /* The path to the symlink on disk (not the path to the target of the link) */
843251881Speter  const char *local_abspath;
844251881Speter
845251881Speter  /* Indicates whether the "normal form" of the symlink has been read. */
846251881Speter  svn_boolean_t at_eof;
847251881Speter};
848251881Speter
849251881Speter/* Allocate *STRINGBUF in RESULT_POOL, and store into it the "normal form"
850251881Speter * of the symlink accessed via BATON.
851251881Speter *
852251881Speter * Otherwise behaves like readline_file(), which see.
853251881Speter */
854251881Speterstatic svn_error_t *
855251881Speterreadline_symlink(void *baton, svn_stringbuf_t **line, const char **eol_str,
856251881Speter                 svn_boolean_t *eof, apr_pool_t *result_pool,
857251881Speter                 apr_pool_t *scratch_pool)
858251881Speter{
859251881Speter  struct symlink_baton_t *sb = baton;
860251881Speter
861251881Speter  if (eof)
862251881Speter    *eof = TRUE;
863251881Speter  if (eol_str)
864251881Speter    *eol_str = NULL;
865251881Speter
866251881Speter  if (sb->at_eof)
867251881Speter    {
868251881Speter      *line = NULL;
869251881Speter    }
870251881Speter  else
871251881Speter    {
872251881Speter      svn_string_t *dest;
873251881Speter
874251881Speter      SVN_ERR(svn_io_read_link(&dest, sb->local_abspath, scratch_pool));
875251881Speter      *line = svn_stringbuf_createf(result_pool, "link %s", dest->data);
876251881Speter      sb->at_eof = TRUE;
877251881Speter    }
878251881Speter
879251881Speter  return SVN_NO_ERROR;
880251881Speter}
881251881Speter
882251881Speter/* Set *OFFSET to 1 or 0 depending on whether the "normal form" of
883251881Speter * the symlink has already been read. */
884251881Speterstatic svn_error_t *
885251881Spetertell_symlink(void *baton, apr_off_t *offset, apr_pool_t *scratch_pool)
886251881Speter{
887251881Speter  struct symlink_baton_t *sb = baton;
888251881Speter
889251881Speter  *offset = sb->at_eof ? 1 : 0;
890251881Speter  return SVN_NO_ERROR;
891251881Speter}
892251881Speter
893251881Speter/* If offset is non-zero, mark the symlink as having been read in its
894251881Speter * "normal form". Else, mark the symlink as not having been read yet. */
895251881Speterstatic svn_error_t *
896251881Speterseek_symlink(void *baton, apr_off_t offset, apr_pool_t *scratch_pool)
897251881Speter{
898251881Speter  struct symlink_baton_t *sb = baton;
899251881Speter
900251881Speter  sb->at_eof = (offset != 0);
901251881Speter  return SVN_NO_ERROR;
902251881Speter}
903251881Speter
904251881Speter
905251881Speter/* Set the target of the symlink accessed via BATON.
906251881Speter * The contents of BUF must be a valid "normal form" of a symlink. */
907251881Speterstatic svn_error_t *
908251881Speterwrite_symlink(void *baton, const char *buf, apr_size_t len,
909251881Speter              apr_pool_t *scratch_pool)
910251881Speter{
911251881Speter  const char *target_abspath = baton;
912251881Speter  const char *new_name;
913251881Speter  const char *link = apr_pstrndup(scratch_pool, buf, len);
914251881Speter
915251881Speter  if (strncmp(link, "link ", 5) != 0)
916251881Speter    return svn_error_create(SVN_ERR_IO_WRITE_ERROR, NULL,
917251881Speter                            _("Invalid link representation"));
918251881Speter
919251881Speter  link += 5; /* Skip "link " */
920251881Speter
921251881Speter  /* We assume the entire symlink is written at once, as the patch
922251881Speter     format is line based */
923251881Speter
924251881Speter  SVN_ERR(svn_io_create_unique_link(&new_name, target_abspath, link,
925251881Speter                                    ".tmp", scratch_pool));
926251881Speter
927251881Speter  SVN_ERR(svn_io_file_rename(new_name, target_abspath, scratch_pool));
928251881Speter
929251881Speter  return SVN_NO_ERROR;
930251881Speter}
931251881Speter
932251881Speter
933251881Speter/* Return a suitable filename for the target of PATCH.
934251881Speter * Examine the ``old'' and ``new'' file names, and choose the file name
935251881Speter * with the fewest path components, the shortest basename, and the shortest
936251881Speter * total file name length (in that order). In case of a tie, return the new
937251881Speter * filename. This heuristic is also used by Larry Wall's UNIX patch (except
938251881Speter * that it prompts for a filename in case of a tie).
939251881Speter * Additionally, for compatibility with git, if one of the filenames
940251881Speter * is "/dev/null", use the other filename. */
941251881Speterstatic const char *
942251881Speterchoose_target_filename(const svn_patch_t *patch)
943251881Speter{
944251881Speter  apr_size_t old;
945251881Speter  apr_size_t new;
946251881Speter
947251881Speter  if (strcmp(patch->old_filename, "/dev/null") == 0)
948251881Speter    return patch->new_filename;
949251881Speter  if (strcmp(patch->new_filename, "/dev/null") == 0)
950251881Speter    return patch->old_filename;
951251881Speter
952299742Sdim  /* If the patch renames the target, use the old name while
953299742Sdim   * applying hunks. The target will be renamed to the new name
954299742Sdim   * after hunks have been applied. */
955299742Sdim  if (patch->operation == svn_diff_op_moved)
956299742Sdim    return patch->old_filename;
957299742Sdim
958251881Speter  old = svn_path_component_count(patch->old_filename);
959251881Speter  new = svn_path_component_count(patch->new_filename);
960251881Speter
961251881Speter  if (old == new)
962251881Speter    {
963251881Speter      old = strlen(svn_dirent_basename(patch->old_filename, NULL));
964251881Speter      new = strlen(svn_dirent_basename(patch->new_filename, NULL));
965251881Speter
966251881Speter      if (old == new)
967251881Speter        {
968251881Speter          old = strlen(patch->old_filename);
969251881Speter          new = strlen(patch->new_filename);
970251881Speter        }
971251881Speter    }
972251881Speter
973251881Speter  return (old < new) ? patch->old_filename : patch->new_filename;
974251881Speter}
975251881Speter
976251881Speter/* Attempt to initialize a *PATCH_TARGET structure for a target file
977251881Speter * described by PATCH. Use working copy context WC_CTX.
978251881Speter * STRIP_COUNT specifies the number of leading path components
979251881Speter * which should be stripped from target paths in the patch.
980251881Speter * The patch target structure is allocated in RESULT_POOL, but if the target
981251881Speter * should be skipped, PATCH_TARGET->SKIPPED is set and the target should be
982251881Speter * treated as not fully initialized, e.g. the caller should not not do any
983251881Speter * further operations on the target if it is marked to be skipped.
984251881Speter * If REMOVE_TEMPFILES is TRUE, set up temporary files to be removed as
985251881Speter * soon as they are no longer needed.
986251881Speter * Use SCRATCH_POOL for all other allocations. */
987251881Speterstatic svn_error_t *
988251881Speterinit_patch_target(patch_target_t **patch_target,
989251881Speter                  const svn_patch_t *patch,
990251881Speter                  const char *wcroot_abspath,
991251881Speter                  svn_wc_context_t *wc_ctx, int strip_count,
992251881Speter                  svn_boolean_t remove_tempfiles,
993251881Speter                  apr_pool_t *result_pool, apr_pool_t *scratch_pool)
994251881Speter{
995251881Speter  patch_target_t *target;
996251881Speter  target_content_t *content;
997251881Speter  svn_boolean_t has_prop_changes = FALSE;
998251881Speter  svn_boolean_t prop_changes_only = FALSE;
999251881Speter
1000251881Speter  {
1001251881Speter    apr_hash_index_t *hi;
1002251881Speter
1003251881Speter    for (hi = apr_hash_first(scratch_pool, patch->prop_patches);
1004251881Speter         hi;
1005251881Speter         hi = apr_hash_next(hi))
1006251881Speter      {
1007299742Sdim        svn_prop_patch_t *prop_patch = apr_hash_this_val(hi);
1008251881Speter        if (! has_prop_changes)
1009251881Speter          has_prop_changes = prop_patch->hunks->nelts > 0;
1010251881Speter        else
1011251881Speter          break;
1012251881Speter      }
1013251881Speter  }
1014251881Speter
1015251881Speter  prop_changes_only = has_prop_changes && patch->hunks->nelts == 0;
1016251881Speter
1017251881Speter  content = apr_pcalloc(result_pool, sizeof(*content));
1018251881Speter
1019251881Speter  /* All other fields in content are FALSE or NULL due to apr_pcalloc().*/
1020251881Speter  content->current_line = 1;
1021251881Speter  content->eol_style = svn_subst_eol_style_none;
1022251881Speter  content->lines = apr_array_make(result_pool, 0, sizeof(apr_off_t));
1023251881Speter  content->hunks = apr_array_make(result_pool, 0, sizeof(hunk_info_t *));
1024251881Speter  content->keywords = apr_hash_make(result_pool);
1025251881Speter
1026251881Speter  target = apr_pcalloc(result_pool, sizeof(*target));
1027251881Speter
1028251881Speter  /* All other fields in target are FALSE or NULL due to apr_pcalloc(). */
1029251881Speter  target->db_kind = svn_node_none;
1030251881Speter  target->kind_on_disk = svn_node_none;
1031251881Speter  target->content = content;
1032251881Speter  target->prop_targets = apr_hash_make(result_pool);
1033251881Speter
1034251881Speter  SVN_ERR(resolve_target_path(target, choose_target_filename(patch),
1035251881Speter                              wcroot_abspath, strip_count, prop_changes_only,
1036251881Speter                              wc_ctx, result_pool, scratch_pool));
1037299742Sdim  *patch_target = target;
1038251881Speter  if (! target->skipped)
1039251881Speter    {
1040251881Speter      const char *diff_header;
1041251881Speter      apr_size_t len;
1042251881Speter
1043251881Speter      /* Create a temporary file to write the patched result to.
1044251881Speter       * Also grab various bits of information about the file. */
1045251881Speter      if (target->is_symlink)
1046251881Speter        {
1047251881Speter          struct symlink_baton_t *sb = apr_pcalloc(result_pool, sizeof(*sb));
1048251881Speter          content->existed = TRUE;
1049251881Speter
1050251881Speter          sb->local_abspath = target->local_abspath;
1051251881Speter
1052251881Speter          /* Wire up the read callbacks. */
1053251881Speter          content->read_baton = sb;
1054251881Speter
1055251881Speter          content->readline = readline_symlink;
1056251881Speter          content->seek = seek_symlink;
1057251881Speter          content->tell = tell_symlink;
1058251881Speter        }
1059251881Speter      else if (target->kind_on_disk == svn_node_file)
1060251881Speter        {
1061251881Speter          SVN_ERR(svn_io_file_open(&target->file, target->local_abspath,
1062251881Speter                                   APR_READ | APR_BUFFERED,
1063251881Speter                                   APR_OS_DEFAULT, result_pool));
1064251881Speter          SVN_ERR(svn_wc_text_modified_p2(&target->local_mods, wc_ctx,
1065251881Speter                                          target->local_abspath, FALSE,
1066251881Speter                                          scratch_pool));
1067251881Speter          SVN_ERR(svn_io_is_file_executable(&target->executable,
1068251881Speter                                            target->local_abspath,
1069251881Speter                                            scratch_pool));
1070251881Speter          SVN_ERR(obtain_eol_and_keywords_for_file(&content->keywords,
1071251881Speter                                                   &content->eol_style,
1072251881Speter                                                   &content->eol_str,
1073251881Speter                                                   wc_ctx,
1074251881Speter                                                   target->local_abspath,
1075251881Speter                                                   result_pool,
1076251881Speter                                                   scratch_pool));
1077251881Speter          content->existed = TRUE;
1078251881Speter
1079251881Speter          /* Wire up the read callbacks. */
1080251881Speter          content->readline = readline_file;
1081251881Speter          content->seek = seek_file;
1082251881Speter          content->tell = tell_file;
1083251881Speter          content->read_baton = target->file;
1084251881Speter        }
1085251881Speter
1086251881Speter      /* ### Is it ok to set the operation of the target already here? Isn't
1087251881Speter       * ### the target supposed to be marked with an operation after we have
1088251881Speter       * ### determined that the changes will apply cleanly to the WC? Maybe
1089251881Speter       * ### we should have kept the patch field in patch_target_t to be
1090251881Speter       * ### able to distinguish between 'what the patch says we should do'
1091251881Speter       * ### and 'what we can do with the given state of our WC'. */
1092251881Speter      if (patch->operation == svn_diff_op_added)
1093251881Speter        target->added = TRUE;
1094251881Speter      else if (patch->operation == svn_diff_op_deleted)
1095251881Speter        target->deleted = TRUE;
1096299742Sdim      else if (patch->operation == svn_diff_op_moved)
1097299742Sdim        {
1098299742Sdim          const char *move_target_path;
1099299742Sdim          const char *move_target_relpath;
1100299742Sdim          svn_boolean_t under_root;
1101299742Sdim          svn_node_kind_t kind_on_disk;
1102299742Sdim          svn_node_kind_t wc_kind;
1103251881Speter
1104299742Sdim          move_target_path = svn_dirent_internal_style(patch->new_filename,
1105299742Sdim                                                       scratch_pool);
1106299742Sdim
1107299742Sdim          if (strip_count > 0)
1108299742Sdim            SVN_ERR(strip_path(&move_target_path, move_target_path,
1109299742Sdim                               strip_count, scratch_pool, scratch_pool));
1110299742Sdim
1111299742Sdim          if (svn_dirent_is_absolute(move_target_path))
1112299742Sdim            {
1113299742Sdim              move_target_relpath = svn_dirent_is_child(wcroot_abspath,
1114299742Sdim                                                        move_target_path,
1115299742Sdim                                                        scratch_pool);
1116299742Sdim              if (! move_target_relpath)
1117299742Sdim                {
1118299742Sdim                  /* The move target path is either outside of the working
1119299742Sdim                   * copy or it is the working copy itself. Skip it. */
1120299742Sdim                  target->skipped = TRUE;
1121299742Sdim                  target->local_abspath = NULL;
1122299742Sdim                  return SVN_NO_ERROR;
1123299742Sdim                }
1124299742Sdim            }
1125299742Sdim          else
1126299742Sdim            move_target_relpath = move_target_path;
1127299742Sdim
1128299742Sdim          /* Make sure the move target path is secure to use. */
1129299742Sdim          SVN_ERR(svn_dirent_is_under_root(&under_root,
1130299742Sdim                                           &target->move_target_abspath,
1131299742Sdim                                           wcroot_abspath,
1132299742Sdim                                           move_target_relpath, result_pool));
1133299742Sdim          if (! under_root)
1134299742Sdim            {
1135299742Sdim              /* The target path is outside of the working copy. Skip it. */
1136299742Sdim              target->skipped = TRUE;
1137299742Sdim              target->local_abspath = NULL;
1138299742Sdim              return SVN_NO_ERROR;
1139299742Sdim            }
1140299742Sdim
1141299742Sdim          SVN_ERR(svn_io_check_path(target->move_target_abspath,
1142299742Sdim                                    &kind_on_disk, scratch_pool));
1143299742Sdim          SVN_ERR(svn_wc_read_kind2(&wc_kind, wc_ctx,
1144299742Sdim                                    target->move_target_abspath,
1145299742Sdim                                    FALSE, FALSE, scratch_pool));
1146299742Sdim          if (kind_on_disk != svn_node_none || wc_kind != svn_node_none)
1147299742Sdim            {
1148299742Sdim              /* The move target path already exists on disk. Skip target. */
1149299742Sdim              target->skipped = TRUE;
1150299742Sdim              target->move_target_abspath = NULL;
1151299742Sdim              return SVN_NO_ERROR;
1152299742Sdim            }
1153299742Sdim        }
1154299742Sdim
1155251881Speter      if (! target->is_symlink)
1156251881Speter        {
1157251881Speter          /* Open a temporary file to write the patched result to. */
1158251881Speter          SVN_ERR(svn_io_open_unique_file3(&target->patched_file,
1159251881Speter                                           &target->patched_path, NULL,
1160251881Speter                                           remove_tempfiles ?
1161251881Speter                                             svn_io_file_del_on_pool_cleanup :
1162251881Speter                                             svn_io_file_del_none,
1163251881Speter                                           result_pool, scratch_pool));
1164251881Speter
1165251881Speter          /* Put the write callback in place. */
1166251881Speter          content->write = write_file;
1167251881Speter          content->write_baton = target->patched_file;
1168251881Speter        }
1169251881Speter      else
1170251881Speter        {
1171251881Speter          /* Put the write callback in place. */
1172251881Speter          SVN_ERR(svn_io_open_unique_file3(NULL,
1173251881Speter                                           &target->patched_path, NULL,
1174251881Speter                                           remove_tempfiles ?
1175251881Speter                                             svn_io_file_del_on_pool_cleanup :
1176251881Speter                                             svn_io_file_del_none,
1177251881Speter                                           result_pool, scratch_pool));
1178251881Speter
1179251881Speter          content->write_baton = (void*)target->patched_path;
1180251881Speter
1181251881Speter          content->write = write_symlink;
1182251881Speter        }
1183251881Speter
1184251881Speter      /* Open a temporary file to write rejected hunks to. */
1185251881Speter      SVN_ERR(svn_io_open_unique_file3(&target->reject_file,
1186251881Speter                                       &target->reject_path, NULL,
1187251881Speter                                       remove_tempfiles ?
1188251881Speter                                         svn_io_file_del_on_pool_cleanup :
1189251881Speter                                         svn_io_file_del_none,
1190251881Speter                                       result_pool, scratch_pool));
1191251881Speter
1192251881Speter      /* The reject file needs a diff header. */
1193251881Speter      diff_header = apr_psprintf(scratch_pool, "--- %s%s+++ %s%s",
1194251881Speter                                 target->canon_path_from_patchfile,
1195251881Speter                                 APR_EOL_STR,
1196251881Speter                                 target->canon_path_from_patchfile,
1197251881Speter                                 APR_EOL_STR);
1198251881Speter      len = strlen(diff_header);
1199251881Speter      SVN_ERR(svn_io_file_write_full(target->reject_file, diff_header, len,
1200251881Speter                                     &len, scratch_pool));
1201251881Speter
1202251881Speter      /* Handle properties. */
1203251881Speter      if (! target->skipped)
1204251881Speter        {
1205251881Speter          apr_hash_index_t *hi;
1206251881Speter
1207251881Speter          for (hi = apr_hash_first(result_pool, patch->prop_patches);
1208251881Speter               hi;
1209251881Speter               hi = apr_hash_next(hi))
1210251881Speter            {
1211299742Sdim              const char *prop_name = apr_hash_this_key(hi);
1212299742Sdim              svn_prop_patch_t *prop_patch = apr_hash_this_val(hi);
1213251881Speter              prop_patch_target_t *prop_target;
1214251881Speter
1215251881Speter              SVN_ERR(init_prop_target(&prop_target,
1216251881Speter                                       prop_name,
1217251881Speter                                       prop_patch->operation,
1218251881Speter                                       wc_ctx, target->local_abspath,
1219251881Speter                                       result_pool, scratch_pool));
1220251881Speter              svn_hash_sets(target->prop_targets, prop_name, prop_target);
1221251881Speter            }
1222251881Speter        }
1223251881Speter    }
1224251881Speter
1225251881Speter  return SVN_NO_ERROR;
1226251881Speter}
1227251881Speter
1228251881Speter/* Read a *LINE from CONTENT. If the line has not been read before
1229251881Speter * mark the line in CONTENT->LINES.
1230251881Speter * If a line could be read successfully, increase CONTENT->CURRENT_LINE,
1231251881Speter * and allocate *LINE in RESULT_POOL.
1232251881Speter * Do temporary allocations in SCRATCH_POOL.
1233251881Speter */
1234251881Speterstatic svn_error_t *
1235251881Speterreadline(target_content_t *content,
1236251881Speter         const char **line,
1237251881Speter         apr_pool_t *result_pool,
1238251881Speter         apr_pool_t *scratch_pool)
1239251881Speter{
1240251881Speter  svn_stringbuf_t *line_raw;
1241251881Speter  const char *eol_str;
1242251881Speter  svn_linenum_t max_line = (svn_linenum_t)content->lines->nelts + 1;
1243251881Speter
1244251881Speter  if (content->eof || content->readline == NULL)
1245251881Speter    {
1246251881Speter      *line = "";
1247251881Speter      return SVN_NO_ERROR;
1248251881Speter    }
1249251881Speter
1250251881Speter  SVN_ERR_ASSERT(content->current_line <= max_line);
1251251881Speter  if (content->current_line == max_line)
1252251881Speter    {
1253251881Speter      apr_off_t offset;
1254251881Speter
1255251881Speter      SVN_ERR(content->tell(content->read_baton, &offset,
1256251881Speter                            scratch_pool));
1257251881Speter      APR_ARRAY_PUSH(content->lines, apr_off_t) = offset;
1258251881Speter    }
1259251881Speter
1260251881Speter  SVN_ERR(content->readline(content->read_baton, &line_raw,
1261251881Speter                            &eol_str, &content->eof,
1262251881Speter                            result_pool, scratch_pool));
1263251881Speter  if (content->eol_style == svn_subst_eol_style_none)
1264251881Speter    content->eol_str = eol_str;
1265251881Speter
1266251881Speter  if (line_raw)
1267251881Speter    {
1268251881Speter      /* Contract keywords. */
1269251881Speter      SVN_ERR(svn_subst_translate_cstring2(line_raw->data, line,
1270251881Speter                                           NULL, FALSE,
1271251881Speter                                           content->keywords, FALSE,
1272251881Speter                                           result_pool));
1273251881Speter    }
1274251881Speter  else
1275251881Speter    *line = "";
1276251881Speter
1277251881Speter  if ((line_raw && line_raw->len > 0) || eol_str)
1278251881Speter    content->current_line++;
1279251881Speter
1280251881Speter  SVN_ERR_ASSERT(content->current_line > 0);
1281251881Speter
1282251881Speter  return SVN_NO_ERROR;
1283251881Speter}
1284251881Speter
1285251881Speter/* Seek to the specified LINE in CONTENT.
1286251881Speter * Mark any lines not read before in CONTENT->LINES.
1287251881Speter * Do temporary allocations in SCRATCH_POOL.
1288251881Speter */
1289251881Speterstatic svn_error_t *
1290251881Speterseek_to_line(target_content_t *content, svn_linenum_t line,
1291251881Speter             apr_pool_t *scratch_pool)
1292251881Speter{
1293251881Speter  svn_linenum_t saved_line;
1294251881Speter  svn_boolean_t saved_eof;
1295251881Speter
1296251881Speter  SVN_ERR_ASSERT(line > 0);
1297251881Speter
1298251881Speter  if (line == content->current_line)
1299251881Speter    return SVN_NO_ERROR;
1300251881Speter
1301251881Speter  saved_line = content->current_line;
1302251881Speter  saved_eof = content->eof;
1303251881Speter
1304251881Speter  if (line <= (svn_linenum_t)content->lines->nelts)
1305251881Speter    {
1306251881Speter      apr_off_t offset;
1307251881Speter
1308251881Speter      offset = APR_ARRAY_IDX(content->lines, line - 1, apr_off_t);
1309251881Speter      SVN_ERR(content->seek(content->read_baton, offset,
1310251881Speter                            scratch_pool));
1311251881Speter      content->current_line = line;
1312251881Speter    }
1313251881Speter  else
1314251881Speter    {
1315251881Speter      const char *dummy;
1316251881Speter      apr_pool_t *iterpool = svn_pool_create(scratch_pool);
1317251881Speter
1318251881Speter      while (! content->eof && content->current_line < line)
1319251881Speter        {
1320251881Speter          svn_pool_clear(iterpool);
1321251881Speter          SVN_ERR(readline(content, &dummy, iterpool, iterpool));
1322251881Speter        }
1323251881Speter      svn_pool_destroy(iterpool);
1324251881Speter    }
1325251881Speter
1326251881Speter  /* After seeking backwards from EOF position clear EOF indicator. */
1327251881Speter  if (saved_eof && saved_line > content->current_line)
1328251881Speter    content->eof = FALSE;
1329251881Speter
1330251881Speter  return SVN_NO_ERROR;
1331251881Speter}
1332251881Speter
1333251881Speter/* Indicate in *MATCHED whether the original text of HUNK matches the patch
1334251881Speter * CONTENT at its current line. Lines within FUZZ lines of the start or
1335251881Speter * end of HUNK will always match. If IGNORE_WHITESPACE is set, we ignore
1336251881Speter * whitespace when doing the matching. When this function returns, neither
1337251881Speter * CONTENT->CURRENT_LINE nor the file offset in the target file will
1338251881Speter * have changed. If MATCH_MODIFIED is TRUE, match the modified hunk text,
1339251881Speter * rather than the original hunk text.
1340251881Speter * Do temporary allocations in POOL. */
1341251881Speterstatic svn_error_t *
1342251881Spetermatch_hunk(svn_boolean_t *matched, target_content_t *content,
1343251881Speter           svn_diff_hunk_t *hunk, svn_linenum_t fuzz,
1344251881Speter           svn_boolean_t ignore_whitespace,
1345251881Speter           svn_boolean_t match_modified, apr_pool_t *pool)
1346251881Speter{
1347251881Speter  svn_stringbuf_t *hunk_line;
1348251881Speter  const char *target_line;
1349251881Speter  svn_linenum_t lines_read;
1350251881Speter  svn_linenum_t saved_line;
1351251881Speter  svn_boolean_t hunk_eof;
1352251881Speter  svn_boolean_t lines_matched;
1353251881Speter  apr_pool_t *iterpool;
1354251881Speter  svn_linenum_t hunk_length;
1355251881Speter  svn_linenum_t leading_context;
1356251881Speter  svn_linenum_t trailing_context;
1357251881Speter
1358251881Speter  *matched = FALSE;
1359251881Speter
1360251881Speter  if (content->eof)
1361251881Speter    return SVN_NO_ERROR;
1362251881Speter
1363251881Speter  saved_line = content->current_line;
1364251881Speter  lines_read = 0;
1365251881Speter  lines_matched = FALSE;
1366251881Speter  leading_context = svn_diff_hunk_get_leading_context(hunk);
1367251881Speter  trailing_context = svn_diff_hunk_get_trailing_context(hunk);
1368251881Speter  if (match_modified)
1369251881Speter    {
1370251881Speter      svn_diff_hunk_reset_modified_text(hunk);
1371251881Speter      hunk_length = svn_diff_hunk_get_modified_length(hunk);
1372251881Speter    }
1373251881Speter  else
1374251881Speter    {
1375251881Speter      svn_diff_hunk_reset_original_text(hunk);
1376251881Speter      hunk_length = svn_diff_hunk_get_original_length(hunk);
1377251881Speter    }
1378251881Speter  iterpool = svn_pool_create(pool);
1379251881Speter  do
1380251881Speter    {
1381251881Speter      const char *hunk_line_translated;
1382251881Speter
1383251881Speter      svn_pool_clear(iterpool);
1384251881Speter
1385251881Speter      if (match_modified)
1386251881Speter        SVN_ERR(svn_diff_hunk_readline_modified_text(hunk, &hunk_line,
1387251881Speter                                                     NULL, &hunk_eof,
1388251881Speter                                                     iterpool, iterpool));
1389251881Speter      else
1390251881Speter        SVN_ERR(svn_diff_hunk_readline_original_text(hunk, &hunk_line,
1391251881Speter                                                     NULL, &hunk_eof,
1392251881Speter                                                     iterpool, iterpool));
1393251881Speter
1394251881Speter      /* Contract keywords, if any, before matching. */
1395251881Speter      SVN_ERR(svn_subst_translate_cstring2(hunk_line->data,
1396251881Speter                                           &hunk_line_translated,
1397251881Speter                                           NULL, FALSE,
1398251881Speter                                           content->keywords, FALSE,
1399251881Speter                                           iterpool));
1400251881Speter      SVN_ERR(readline(content, &target_line, iterpool, iterpool));
1401251881Speter
1402251881Speter      lines_read++;
1403251881Speter
1404251881Speter      /* If the last line doesn't have a newline, we get EOF but still
1405251881Speter       * have a non-empty line to compare. */
1406251881Speter      if ((hunk_eof && hunk_line->len == 0) ||
1407251881Speter          (content->eof && *target_line == 0))
1408251881Speter        break;
1409251881Speter
1410251881Speter      /* Leading/trailing fuzzy lines always match. */
1411251881Speter      if ((lines_read <= fuzz && leading_context > fuzz) ||
1412251881Speter          (lines_read > hunk_length - fuzz && trailing_context > fuzz))
1413251881Speter        lines_matched = TRUE;
1414251881Speter      else
1415251881Speter        {
1416251881Speter          if (ignore_whitespace)
1417251881Speter            {
1418251881Speter              char *hunk_line_trimmed;
1419251881Speter              char *target_line_trimmed;
1420251881Speter
1421251881Speter              hunk_line_trimmed = apr_pstrdup(iterpool, hunk_line_translated);
1422251881Speter              target_line_trimmed = apr_pstrdup(iterpool, target_line);
1423251881Speter              apr_collapse_spaces(hunk_line_trimmed, hunk_line_trimmed);
1424251881Speter              apr_collapse_spaces(target_line_trimmed, target_line_trimmed);
1425251881Speter              lines_matched = ! strcmp(hunk_line_trimmed, target_line_trimmed);
1426251881Speter            }
1427251881Speter          else
1428251881Speter            lines_matched = ! strcmp(hunk_line_translated, target_line);
1429251881Speter        }
1430251881Speter    }
1431251881Speter  while (lines_matched);
1432251881Speter
1433251881Speter  *matched = lines_matched && hunk_eof && hunk_line->len == 0;
1434251881Speter  SVN_ERR(seek_to_line(content, saved_line, iterpool));
1435251881Speter  svn_pool_destroy(iterpool);
1436251881Speter
1437251881Speter  return SVN_NO_ERROR;
1438251881Speter}
1439251881Speter
1440251881Speter/* Scan lines of CONTENT for a match of the original text of HUNK,
1441251881Speter * up to but not including the specified UPPER_LINE. Use fuzz factor FUZZ.
1442251881Speter * If UPPER_LINE is zero scan until EOF occurs when reading from TARGET.
1443251881Speter * Return the line at which HUNK was matched in *MATCHED_LINE.
1444251881Speter * If the hunk did not match at all, set *MATCHED_LINE to zero.
1445251881Speter * If the hunk matched multiple times, and MATCH_FIRST is TRUE,
1446251881Speter * return the line number at which the first match occurred in *MATCHED_LINE.
1447251881Speter * If the hunk matched multiple times, and MATCH_FIRST is FALSE,
1448251881Speter * return the line number at which the last match occurred in *MATCHED_LINE.
1449251881Speter * If IGNORE_WHITESPACE is set, ignore whitespace during the matching.
1450251881Speter * If MATCH_MODIFIED is TRUE, match the modified hunk text,
1451251881Speter * rather than the original hunk text.
1452251881Speter * Call cancel CANCEL_FUNC with baton CANCEL_BATON to trigger cancellation.
1453251881Speter * Do all allocations in POOL. */
1454251881Speterstatic svn_error_t *
1455251881Speterscan_for_match(svn_linenum_t *matched_line,
1456251881Speter               target_content_t *content,
1457251881Speter               svn_diff_hunk_t *hunk, svn_boolean_t match_first,
1458251881Speter               svn_linenum_t upper_line, svn_linenum_t fuzz,
1459251881Speter               svn_boolean_t ignore_whitespace,
1460251881Speter               svn_boolean_t match_modified,
1461251881Speter               svn_cancel_func_t cancel_func, void *cancel_baton,
1462251881Speter               apr_pool_t *pool)
1463251881Speter{
1464251881Speter  apr_pool_t *iterpool;
1465251881Speter
1466251881Speter  *matched_line = 0;
1467251881Speter  iterpool = svn_pool_create(pool);
1468251881Speter  while ((content->current_line < upper_line || upper_line == 0) &&
1469251881Speter         ! content->eof)
1470251881Speter    {
1471251881Speter      svn_boolean_t matched;
1472251881Speter
1473251881Speter      svn_pool_clear(iterpool);
1474251881Speter
1475251881Speter      if (cancel_func)
1476251881Speter        SVN_ERR(cancel_func(cancel_baton));
1477251881Speter
1478251881Speter      SVN_ERR(match_hunk(&matched, content, hunk, fuzz, ignore_whitespace,
1479251881Speter                         match_modified, iterpool));
1480251881Speter      if (matched)
1481251881Speter        {
1482251881Speter          svn_boolean_t taken = FALSE;
1483251881Speter          int i;
1484251881Speter
1485251881Speter          /* Don't allow hunks to match at overlapping locations. */
1486251881Speter          for (i = 0; i < content->hunks->nelts; i++)
1487251881Speter            {
1488251881Speter              const hunk_info_t *hi;
1489251881Speter              svn_linenum_t length;
1490251881Speter
1491251881Speter              hi = APR_ARRAY_IDX(content->hunks, i, const hunk_info_t *);
1492251881Speter
1493251881Speter              if (match_modified)
1494251881Speter                length = svn_diff_hunk_get_modified_length(hi->hunk);
1495251881Speter              else
1496251881Speter                length = svn_diff_hunk_get_original_length(hi->hunk);
1497251881Speter
1498251881Speter              taken = (! hi->rejected &&
1499251881Speter                       content->current_line >= hi->matched_line &&
1500251881Speter                       content->current_line < (hi->matched_line + length));
1501251881Speter              if (taken)
1502251881Speter                break;
1503251881Speter            }
1504251881Speter
1505251881Speter          if (! taken)
1506251881Speter            {
1507251881Speter              *matched_line = content->current_line;
1508251881Speter              if (match_first)
1509251881Speter                break;
1510251881Speter            }
1511251881Speter        }
1512251881Speter
1513251881Speter      if (! content->eof)
1514251881Speter        SVN_ERR(seek_to_line(content, content->current_line + 1,
1515251881Speter                             iterpool));
1516251881Speter    }
1517251881Speter  svn_pool_destroy(iterpool);
1518251881Speter
1519251881Speter  return SVN_NO_ERROR;
1520251881Speter}
1521251881Speter
1522251881Speter/* Indicate in *MATCH whether the content described by CONTENT
1523251881Speter * matches the modified text of HUNK.
1524251881Speter * Use SCRATCH_POOL for temporary allocations. */
1525251881Speterstatic svn_error_t *
1526251881Spetermatch_existing_target(svn_boolean_t *match,
1527251881Speter                      target_content_t *content,
1528251881Speter                      svn_diff_hunk_t *hunk,
1529251881Speter                      apr_pool_t *scratch_pool)
1530251881Speter{
1531251881Speter  svn_boolean_t lines_matched;
1532251881Speter  apr_pool_t *iterpool;
1533251881Speter  svn_boolean_t hunk_eof;
1534251881Speter  svn_linenum_t saved_line;
1535251881Speter
1536251881Speter  svn_diff_hunk_reset_modified_text(hunk);
1537251881Speter
1538251881Speter  saved_line = content->current_line;
1539251881Speter
1540251881Speter  iterpool = svn_pool_create(scratch_pool);
1541251881Speter  do
1542251881Speter    {
1543251881Speter      const char *line;
1544251881Speter      svn_stringbuf_t *hunk_line;
1545251881Speter      const char *line_translated;
1546251881Speter      const char *hunk_line_translated;
1547251881Speter
1548251881Speter      svn_pool_clear(iterpool);
1549251881Speter
1550251881Speter      SVN_ERR(readline(content, &line, iterpool, iterpool));
1551251881Speter      SVN_ERR(svn_diff_hunk_readline_modified_text(hunk, &hunk_line,
1552251881Speter                                                   NULL, &hunk_eof,
1553251881Speter                                                   iterpool, iterpool));
1554251881Speter      /* Contract keywords. */
1555251881Speter      SVN_ERR(svn_subst_translate_cstring2(line, &line_translated,
1556251881Speter                                           NULL, FALSE,
1557251881Speter                                           content->keywords,
1558251881Speter                                           FALSE, iterpool));
1559251881Speter      SVN_ERR(svn_subst_translate_cstring2(hunk_line->data,
1560251881Speter                                           &hunk_line_translated,
1561251881Speter                                           NULL, FALSE,
1562251881Speter                                           content->keywords,
1563251881Speter                                           FALSE, iterpool));
1564251881Speter      lines_matched = ! strcmp(line_translated, hunk_line_translated);
1565251881Speter      if (content->eof != hunk_eof)
1566251881Speter        {
1567251881Speter          svn_pool_destroy(iterpool);
1568251881Speter          *match = FALSE;
1569251881Speter          return SVN_NO_ERROR;
1570251881Speter        }
1571251881Speter      }
1572251881Speter    while (lines_matched && ! content->eof && ! hunk_eof);
1573251881Speter    svn_pool_destroy(iterpool);
1574251881Speter
1575251881Speter    *match = (lines_matched && content->eof == hunk_eof);
1576251881Speter    SVN_ERR(seek_to_line(content, saved_line, scratch_pool));
1577251881Speter
1578251881Speter    return SVN_NO_ERROR;
1579251881Speter}
1580251881Speter
1581251881Speter/* Determine the line at which a HUNK applies to CONTENT of the TARGET
1582251881Speter * file, and return an appropriate hunk_info object in *HI, allocated from
1583251881Speter * RESULT_POOL. Use fuzz factor FUZZ. Set HI->FUZZ to FUZZ. If no correct
1584299742Sdim * line can be determined, set HI->REJECTED to TRUE.  PREVIOUS_OFFSET
1585299742Sdim * is the offset at which the previous matching hunk was applied, or zero.
1586251881Speter * IGNORE_WHITESPACE tells whether whitespace should be considered when
1587251881Speter * matching. IS_PROP_HUNK indicates whether the hunk patches file content
1588251881Speter * or a property.
1589251881Speter * When this function returns, neither CONTENT->CURRENT_LINE nor
1590251881Speter * the file offset in the target file will have changed.
1591251881Speter * Call cancel CANCEL_FUNC with baton CANCEL_BATON to trigger cancellation.
1592251881Speter * Do temporary allocations in POOL. */
1593251881Speterstatic svn_error_t *
1594251881Speterget_hunk_info(hunk_info_t **hi, patch_target_t *target,
1595251881Speter              target_content_t *content,
1596251881Speter              svn_diff_hunk_t *hunk, svn_linenum_t fuzz,
1597299742Sdim              svn_linenum_t previous_offset,
1598251881Speter              svn_boolean_t ignore_whitespace,
1599251881Speter              svn_boolean_t is_prop_hunk,
1600251881Speter              svn_cancel_func_t cancel_func, void *cancel_baton,
1601251881Speter              apr_pool_t *result_pool, apr_pool_t *scratch_pool)
1602251881Speter{
1603251881Speter  svn_linenum_t matched_line;
1604251881Speter  svn_linenum_t original_start;
1605251881Speter  svn_boolean_t already_applied;
1606251881Speter
1607299742Sdim  original_start = svn_diff_hunk_get_original_start(hunk) + previous_offset;
1608251881Speter  already_applied = FALSE;
1609251881Speter
1610251881Speter  /* An original offset of zero means that this hunk wants to create
1611251881Speter   * a new file. Don't bother matching hunks in that case, since
1612251881Speter   * the hunk applies at line 1. If the file already exists, the hunk
1613251881Speter   * is rejected, unless the file is versioned and its content matches
1614251881Speter   * the file the patch wants to create.  */
1615251881Speter  if (original_start == 0 && fuzz > 0)
1616251881Speter    {
1617251881Speter      matched_line = 0; /* reject any fuzz for new files */
1618251881Speter    }
1619251881Speter  else if (original_start == 0 && ! is_prop_hunk)
1620251881Speter    {
1621251881Speter      if (target->kind_on_disk == svn_node_file)
1622251881Speter        {
1623251881Speter          const svn_io_dirent2_t *dirent;
1624251881Speter          SVN_ERR(svn_io_stat_dirent2(&dirent, target->local_abspath, FALSE,
1625251881Speter                                      TRUE, scratch_pool, scratch_pool));
1626251881Speter
1627251881Speter          if (dirent->kind == svn_node_file
1628251881Speter              && !dirent->special
1629251881Speter              && dirent->filesize == 0)
1630251881Speter            {
1631251881Speter              matched_line = 1; /* Matched an on-disk empty file */
1632251881Speter            }
1633251881Speter          else
1634251881Speter            {
1635251881Speter              if (target->db_kind == svn_node_file)
1636251881Speter                {
1637251881Speter                  svn_boolean_t file_matches;
1638251881Speter
1639251881Speter                  /* ### I can't reproduce anything but a no-match here.
1640251881Speter                         The content is already at eof, so any hunk fails */
1641251881Speter                  SVN_ERR(match_existing_target(&file_matches, content, hunk,
1642251881Speter                                            scratch_pool));
1643251881Speter                  if (file_matches)
1644251881Speter                    {
1645251881Speter                      matched_line = 1;
1646251881Speter                      already_applied = TRUE;
1647251881Speter                    }
1648251881Speter                  else
1649251881Speter                    matched_line = 0; /* reject */
1650251881Speter                }
1651251881Speter              else
1652251881Speter                matched_line = 0; /* reject */
1653251881Speter            }
1654251881Speter        }
1655251881Speter      else
1656251881Speter        matched_line = 1;
1657251881Speter    }
1658251881Speter  /* Same conditions apply as for the file case above.
1659251881Speter   *
1660251881Speter   * ### Since the hunk says the prop should be added we just assume so for
1661251881Speter   * ### now and don't bother with storing the previous lines and such. When
1662251881Speter   * ### we have the diff operation available we can just check for adds. */
1663251881Speter  else if (original_start == 0 && is_prop_hunk)
1664251881Speter    {
1665251881Speter      if (content->existed)
1666251881Speter        {
1667251881Speter          svn_boolean_t prop_matches;
1668251881Speter
1669251881Speter          SVN_ERR(match_existing_target(&prop_matches, content, hunk,
1670251881Speter                                        scratch_pool));
1671251881Speter
1672251881Speter          if (prop_matches)
1673251881Speter            {
1674251881Speter              matched_line = 1;
1675251881Speter              already_applied = TRUE;
1676251881Speter            }
1677251881Speter          else
1678251881Speter            matched_line = 0; /* reject */
1679251881Speter        }
1680251881Speter      else
1681251881Speter        matched_line = 1;
1682251881Speter    }
1683251881Speter  else if (original_start > 0 && content->existed)
1684251881Speter    {
1685251881Speter      svn_linenum_t saved_line = content->current_line;
1686251881Speter
1687251881Speter      /* Scan for a match at the line where the hunk thinks it
1688251881Speter       * should be going. */
1689251881Speter      SVN_ERR(seek_to_line(content, original_start, scratch_pool));
1690251881Speter      if (content->current_line != original_start)
1691251881Speter        {
1692251881Speter          /* Seek failed. */
1693251881Speter          matched_line = 0;
1694251881Speter        }
1695251881Speter      else
1696251881Speter        SVN_ERR(scan_for_match(&matched_line, content, hunk, TRUE,
1697251881Speter                               original_start + 1, fuzz,
1698251881Speter                               ignore_whitespace, FALSE,
1699251881Speter                               cancel_func, cancel_baton,
1700251881Speter                               scratch_pool));
1701251881Speter
1702251881Speter      if (matched_line != original_start)
1703251881Speter        {
1704251881Speter          /* Check if the hunk is already applied.
1705251881Speter           * We only check for an exact match here, and don't bother checking
1706251881Speter           * for already applied patches with offset/fuzz, because such a
1707251881Speter           * check would be ambiguous. */
1708251881Speter          if (fuzz == 0)
1709251881Speter            {
1710251881Speter              svn_linenum_t modified_start;
1711251881Speter
1712251881Speter              modified_start = svn_diff_hunk_get_modified_start(hunk);
1713251881Speter              if (modified_start == 0)
1714251881Speter                {
1715299742Sdim                  /* Patch wants to delete the file.
1716299742Sdim
1717299742Sdim                     ### locally_deleted is always false here? */
1718251881Speter                  already_applied = target->locally_deleted;
1719251881Speter                }
1720251881Speter              else
1721251881Speter                {
1722251881Speter                  SVN_ERR(seek_to_line(content, modified_start,
1723251881Speter                                       scratch_pool));
1724251881Speter                  SVN_ERR(scan_for_match(&matched_line, content,
1725251881Speter                                         hunk, TRUE,
1726251881Speter                                         modified_start + 1,
1727251881Speter                                         fuzz, ignore_whitespace, TRUE,
1728251881Speter                                         cancel_func, cancel_baton,
1729251881Speter                                         scratch_pool));
1730251881Speter                  already_applied = (matched_line == modified_start);
1731251881Speter                }
1732251881Speter            }
1733251881Speter          else
1734251881Speter            already_applied = FALSE;
1735251881Speter
1736251881Speter          if (! already_applied)
1737251881Speter            {
1738299742Sdim              int i;
1739299742Sdim              svn_linenum_t search_start = 1, search_end = 0;
1740299742Sdim              svn_linenum_t matched_line2;
1741251881Speter
1742299742Sdim              /* Search for closest match before or after original
1743299742Sdim                 start.  We have no backward search so search forwards
1744299742Sdim                 from the previous match (or start of file) to the
1745299742Sdim                 original start looking for the last match.  Then
1746299742Sdim                 search forwards from the original start looking for a
1747299742Sdim                 better match.  Finally search forwards from the start
1748299742Sdim                 of file to the previous hunk if that could result in
1749299742Sdim                 a better match. */
1750299742Sdim
1751299742Sdim              for (i = content->hunks->nelts; i > 0; --i)
1752299742Sdim                {
1753299742Sdim                  const hunk_info_t *prev
1754299742Sdim                    = APR_ARRAY_IDX(content->hunks, i - 1, const hunk_info_t *);
1755299742Sdim                  if (!prev->rejected)
1756299742Sdim                    {
1757299742Sdim                      svn_linenum_t length;
1758299742Sdim
1759299742Sdim                      length = svn_diff_hunk_get_original_length(prev->hunk);
1760299742Sdim                      search_start = prev->matched_line + length;
1761299742Sdim                      break;
1762299742Sdim                    }
1763299742Sdim                }
1764299742Sdim
1765299742Sdim              /* Search from the previous match, or start of file,
1766299742Sdim                 towards the original location. */
1767299742Sdim              SVN_ERR(seek_to_line(content, search_start, scratch_pool));
1768251881Speter              SVN_ERR(scan_for_match(&matched_line, content, hunk, FALSE,
1769251881Speter                                     original_start, fuzz,
1770251881Speter                                     ignore_whitespace, FALSE,
1771251881Speter                                     cancel_func, cancel_baton,
1772251881Speter                                     scratch_pool));
1773251881Speter
1774299742Sdim              /* If a match we only need to search forwards for a
1775299742Sdim                 better match, otherwise to the end of the file. */
1776299742Sdim              if (matched_line)
1777299742Sdim                search_end = original_start + (original_start - matched_line);
1778299742Sdim
1779299742Sdim              /* Search from original location, towards the end. */
1780299742Sdim              SVN_ERR(seek_to_line(content, original_start + 1, scratch_pool));
1781299742Sdim              SVN_ERR(scan_for_match(&matched_line2, content, hunk,
1782299742Sdim                                     TRUE, search_end, fuzz, ignore_whitespace,
1783299742Sdim                                     FALSE, cancel_func, cancel_baton,
1784299742Sdim                                     scratch_pool));
1785299742Sdim
1786299742Sdim              /* Chose the forward match if it is closer than the
1787299742Sdim                 backward match or if there is no backward match. */
1788299742Sdim              if (matched_line2
1789299742Sdim                  && (!matched_line
1790299742Sdim                      || (matched_line2 - original_start
1791299742Sdim                          < original_start - matched_line)))
1792299742Sdim                  matched_line = matched_line2;
1793299742Sdim
1794299742Sdim              /* Search from before previous hunk if there could be a
1795299742Sdim                 better match. */
1796299742Sdim              if (search_start > 1
1797299742Sdim                  && (!matched_line
1798299742Sdim                      || (matched_line > original_start
1799299742Sdim                          && (matched_line - original_start
1800299742Sdim                              > original_start - search_start))))
1801251881Speter                {
1802299742Sdim                  svn_linenum_t search_start2 = 1;
1803299742Sdim
1804299742Sdim                  if (matched_line
1805299742Sdim                      && matched_line - original_start < original_start)
1806299742Sdim                    search_start2
1807299742Sdim                      = original_start - (matched_line - original_start) + 1;
1808299742Sdim
1809299742Sdim                  SVN_ERR(seek_to_line(content, search_start2, scratch_pool));
1810299742Sdim                  SVN_ERR(scan_for_match(&matched_line2, content, hunk, FALSE,
1811299742Sdim                                         search_start - 1, fuzz,
1812299742Sdim                                         ignore_whitespace, FALSE,
1813299742Sdim                                         cancel_func, cancel_baton,
1814251881Speter                                         scratch_pool));
1815299742Sdim                  if (matched_line2)
1816299742Sdim                    matched_line = matched_line2;
1817251881Speter                }
1818251881Speter            }
1819251881Speter        }
1820251881Speter
1821251881Speter      SVN_ERR(seek_to_line(content, saved_line, scratch_pool));
1822251881Speter    }
1823251881Speter  else
1824251881Speter    {
1825251881Speter      /* The hunk wants to modify a file which doesn't exist. */
1826251881Speter      matched_line = 0;
1827251881Speter    }
1828251881Speter
1829251881Speter  (*hi) = apr_pcalloc(result_pool, sizeof(hunk_info_t));
1830251881Speter  (*hi)->hunk = hunk;
1831251881Speter  (*hi)->matched_line = matched_line;
1832251881Speter  (*hi)->rejected = (matched_line == 0);
1833251881Speter  (*hi)->already_applied = already_applied;
1834251881Speter  (*hi)->fuzz = fuzz;
1835251881Speter
1836251881Speter  return SVN_NO_ERROR;
1837251881Speter}
1838251881Speter
1839251881Speter/* Copy lines to the patched content until the specified LINE has been
1840251881Speter * reached. Indicate in *EOF whether end-of-file was encountered while
1841251881Speter * reading from the target.
1842251881Speter * If LINE is zero, copy lines until end-of-file has been reached.
1843251881Speter * Do all allocations in POOL. */
1844251881Speterstatic svn_error_t *
1845251881Spetercopy_lines_to_target(target_content_t *content, svn_linenum_t line,
1846251881Speter                     apr_pool_t *pool)
1847251881Speter{
1848251881Speter  apr_pool_t *iterpool;
1849251881Speter
1850251881Speter  iterpool = svn_pool_create(pool);
1851251881Speter  while ((content->current_line < line || line == 0) && ! content->eof)
1852251881Speter    {
1853251881Speter      const char *target_line;
1854251881Speter      apr_size_t len;
1855251881Speter
1856251881Speter      svn_pool_clear(iterpool);
1857251881Speter
1858251881Speter      SVN_ERR(readline(content, &target_line, iterpool, iterpool));
1859251881Speter      if (! content->eof)
1860251881Speter        target_line = apr_pstrcat(iterpool, target_line, content->eol_str,
1861299742Sdim                                  SVN_VA_NULL);
1862251881Speter      len = strlen(target_line);
1863251881Speter      SVN_ERR(content->write(content->write_baton, target_line,
1864251881Speter                             len, iterpool));
1865251881Speter    }
1866251881Speter  svn_pool_destroy(iterpool);
1867251881Speter
1868251881Speter  return SVN_NO_ERROR;
1869251881Speter}
1870251881Speter
1871251881Speter/* Write the diff text of HUNK to TARGET's reject file,
1872251881Speter * and mark TARGET as having had rejects.
1873251881Speter * We don't expand keywords, nor normalise line-endings, in reject files.
1874251881Speter * Do temporary allocations in SCRATCH_POOL. */
1875251881Speterstatic svn_error_t *
1876251881Speterreject_hunk(patch_target_t *target, target_content_t *content,
1877251881Speter            svn_diff_hunk_t *hunk, const char *prop_name,
1878251881Speter            apr_pool_t *pool)
1879251881Speter{
1880251881Speter  const char *hunk_header;
1881251881Speter  apr_size_t len;
1882251881Speter  svn_boolean_t eof;
1883251881Speter  static const char * const text_atat = "@@";
1884251881Speter  static const char * const prop_atat = "##";
1885251881Speter  const char *atat;
1886251881Speter  apr_pool_t *iterpool;
1887251881Speter
1888251881Speter  if (prop_name)
1889251881Speter    {
1890251881Speter      const char *prop_header;
1891251881Speter
1892251881Speter      /* ### Print 'Added', 'Deleted' or 'Modified' instead of 'Property'.
1893251881Speter       */
1894251881Speter      prop_header = apr_psprintf(pool, "Property: %s\n", prop_name);
1895251881Speter      len = strlen(prop_header);
1896251881Speter      SVN_ERR(svn_io_file_write_full(target->reject_file, prop_header,
1897251881Speter                                     len, &len, pool));
1898251881Speter      atat = prop_atat;
1899251881Speter    }
1900251881Speter  else
1901251881Speter    {
1902251881Speter      atat = text_atat;
1903251881Speter    }
1904251881Speter
1905251881Speter  hunk_header = apr_psprintf(pool, "%s -%lu,%lu +%lu,%lu %s%s",
1906251881Speter                             atat,
1907251881Speter                             svn_diff_hunk_get_original_start(hunk),
1908251881Speter                             svn_diff_hunk_get_original_length(hunk),
1909251881Speter                             svn_diff_hunk_get_modified_start(hunk),
1910251881Speter                             svn_diff_hunk_get_modified_length(hunk),
1911251881Speter                             atat,
1912251881Speter                             APR_EOL_STR);
1913251881Speter  len = strlen(hunk_header);
1914251881Speter  SVN_ERR(svn_io_file_write_full(target->reject_file, hunk_header, len,
1915251881Speter                                 &len, pool));
1916251881Speter
1917251881Speter  iterpool = svn_pool_create(pool);
1918251881Speter  do
1919251881Speter    {
1920251881Speter      svn_stringbuf_t *hunk_line;
1921251881Speter      const char *eol_str;
1922251881Speter
1923251881Speter      svn_pool_clear(iterpool);
1924251881Speter
1925251881Speter      SVN_ERR(svn_diff_hunk_readline_diff_text(hunk, &hunk_line, &eol_str,
1926251881Speter                                               &eof, iterpool, iterpool));
1927251881Speter      if (! eof)
1928251881Speter        {
1929251881Speter          if (hunk_line->len >= 1)
1930251881Speter            {
1931251881Speter              len = hunk_line->len;
1932251881Speter              SVN_ERR(svn_io_file_write_full(target->reject_file,
1933251881Speter                                             hunk_line->data, len, &len,
1934251881Speter                                             iterpool));
1935251881Speter            }
1936251881Speter
1937251881Speter          if (eol_str)
1938251881Speter            {
1939251881Speter              len = strlen(eol_str);
1940251881Speter              SVN_ERR(svn_io_file_write_full(target->reject_file, eol_str,
1941251881Speter                                             len, &len, iterpool));
1942251881Speter            }
1943251881Speter        }
1944251881Speter    }
1945251881Speter  while (! eof);
1946251881Speter  svn_pool_destroy(iterpool);
1947251881Speter
1948251881Speter  if (prop_name)
1949251881Speter    target->had_prop_rejects = TRUE;
1950251881Speter  else
1951251881Speter    target->had_rejects = TRUE;
1952251881Speter
1953251881Speter  return SVN_NO_ERROR;
1954251881Speter}
1955251881Speter
1956251881Speter/* Write the modified text of the hunk described by HI to the patched
1957251881Speter * CONTENT. TARGET is the patch target.
1958251881Speter * If PROP_NAME is not NULL, the hunk is assumed to be targeted for
1959251881Speter * a property with the given name.
1960251881Speter * Do temporary allocations in POOL. */
1961251881Speterstatic svn_error_t *
1962251881Speterapply_hunk(patch_target_t *target, target_content_t *content,
1963251881Speter           hunk_info_t *hi, const char *prop_name, apr_pool_t *pool)
1964251881Speter{
1965251881Speter  svn_linenum_t lines_read;
1966251881Speter  svn_boolean_t eof;
1967251881Speter  apr_pool_t *iterpool;
1968251881Speter
1969251881Speter  /* ### Is there a cleaner way to describe if we have an existing target?
1970251881Speter   */
1971251881Speter  if (target->kind_on_disk == svn_node_file || prop_name)
1972251881Speter    {
1973251881Speter      svn_linenum_t line;
1974251881Speter
1975251881Speter      /* Move forward to the hunk's line, copying data as we go.
1976251881Speter       * Also copy leading lines of context which matched with fuzz.
1977251881Speter       * The target has changed on the fuzzy-matched lines,
1978251881Speter       * so we should retain the target's version of those lines. */
1979251881Speter      SVN_ERR(copy_lines_to_target(content, hi->matched_line + hi->fuzz,
1980251881Speter                                   pool));
1981251881Speter
1982251881Speter      /* Skip the target's version of the hunk.
1983251881Speter       * Don't skip trailing lines which matched with fuzz. */
1984251881Speter      line = content->current_line +
1985251881Speter             svn_diff_hunk_get_original_length(hi->hunk) - (2 * hi->fuzz);
1986251881Speter      SVN_ERR(seek_to_line(content, line, pool));
1987251881Speter      if (content->current_line != line && ! content->eof)
1988251881Speter        {
1989251881Speter          /* Seek failed, reject this hunk. */
1990251881Speter          hi->rejected = TRUE;
1991251881Speter          SVN_ERR(reject_hunk(target, content, hi->hunk, prop_name, pool));
1992251881Speter          return SVN_NO_ERROR;
1993251881Speter        }
1994251881Speter    }
1995251881Speter
1996251881Speter  /* Write the hunk's version to the patched result.
1997251881Speter   * Don't write the lines which matched with fuzz. */
1998251881Speter  lines_read = 0;
1999251881Speter  svn_diff_hunk_reset_modified_text(hi->hunk);
2000251881Speter  iterpool = svn_pool_create(pool);
2001251881Speter  do
2002251881Speter    {
2003251881Speter      svn_stringbuf_t *hunk_line;
2004251881Speter      const char *eol_str;
2005251881Speter
2006251881Speter      svn_pool_clear(iterpool);
2007251881Speter
2008251881Speter      SVN_ERR(svn_diff_hunk_readline_modified_text(hi->hunk, &hunk_line,
2009251881Speter                                                   &eol_str, &eof,
2010251881Speter                                                   iterpool, iterpool));
2011251881Speter      lines_read++;
2012251881Speter      if (lines_read > hi->fuzz &&
2013251881Speter          lines_read <= svn_diff_hunk_get_modified_length(hi->hunk) - hi->fuzz)
2014251881Speter        {
2015251881Speter          apr_size_t len;
2016251881Speter
2017251881Speter          if (hunk_line->len >= 1)
2018251881Speter            {
2019251881Speter              len = hunk_line->len;
2020251881Speter              SVN_ERR(content->write(content->write_baton,
2021251881Speter                                     hunk_line->data, len, iterpool));
2022251881Speter            }
2023251881Speter
2024251881Speter          if (eol_str)
2025251881Speter            {
2026251881Speter              /* Use the EOL as it was read from the patch file,
2027251881Speter               * unless the target's EOL style is set by svn:eol-style */
2028251881Speter              if (content->eol_style != svn_subst_eol_style_none)
2029251881Speter                eol_str = content->eol_str;
2030251881Speter
2031251881Speter              len = strlen(eol_str);
2032251881Speter              SVN_ERR(content->write(content->write_baton,
2033251881Speter                                     eol_str, len, iterpool));
2034251881Speter            }
2035251881Speter        }
2036251881Speter    }
2037251881Speter  while (! eof);
2038251881Speter  svn_pool_destroy(iterpool);
2039251881Speter
2040251881Speter  if (prop_name)
2041251881Speter    target->has_prop_changes = TRUE;
2042251881Speter  else
2043251881Speter    target->has_text_changes = TRUE;
2044251881Speter
2045251881Speter  return SVN_NO_ERROR;
2046251881Speter}
2047251881Speter
2048251881Speter/* Use client context CTX to send a suitable notification for hunk HI,
2049251881Speter * using TARGET to determine the path. If the hunk is a property hunk,
2050251881Speter * PROP_NAME must be the name of the property, else NULL.
2051251881Speter * Use POOL for temporary allocations. */
2052251881Speterstatic svn_error_t *
2053251881Spetersend_hunk_notification(const hunk_info_t *hi,
2054251881Speter                       const patch_target_t *target,
2055251881Speter                       const char *prop_name,
2056251881Speter                       const svn_client_ctx_t *ctx,
2057251881Speter                       apr_pool_t *pool)
2058251881Speter{
2059251881Speter  svn_wc_notify_t *notify;
2060251881Speter  svn_wc_notify_action_t action;
2061251881Speter
2062251881Speter  if (hi->already_applied)
2063251881Speter    action = svn_wc_notify_patch_hunk_already_applied;
2064251881Speter  else if (hi->rejected)
2065251881Speter    action = svn_wc_notify_patch_rejected_hunk;
2066251881Speter  else
2067251881Speter    action = svn_wc_notify_patch_applied_hunk;
2068251881Speter
2069251881Speter  notify = svn_wc_create_notify(target->local_abspath
2070251881Speter                                    ? target->local_abspath
2071251881Speter                                    : target->local_relpath,
2072251881Speter                                action, pool);
2073251881Speter  notify->hunk_original_start =
2074251881Speter    svn_diff_hunk_get_original_start(hi->hunk);
2075251881Speter  notify->hunk_original_length =
2076251881Speter    svn_diff_hunk_get_original_length(hi->hunk);
2077251881Speter  notify->hunk_modified_start =
2078251881Speter    svn_diff_hunk_get_modified_start(hi->hunk);
2079251881Speter  notify->hunk_modified_length =
2080251881Speter    svn_diff_hunk_get_modified_length(hi->hunk);
2081251881Speter  notify->hunk_matched_line = hi->matched_line;
2082251881Speter  notify->hunk_fuzz = hi->fuzz;
2083251881Speter  notify->prop_name = prop_name;
2084251881Speter
2085299742Sdim  ctx->notify_func2(ctx->notify_baton2, notify, pool);
2086251881Speter
2087251881Speter  return SVN_NO_ERROR;
2088251881Speter}
2089251881Speter
2090251881Speter/* Use client context CTX to send a suitable notification for a patch TARGET.
2091251881Speter * Use POOL for temporary allocations. */
2092251881Speterstatic svn_error_t *
2093251881Spetersend_patch_notification(const patch_target_t *target,
2094251881Speter                        const svn_client_ctx_t *ctx,
2095251881Speter                        apr_pool_t *pool)
2096251881Speter{
2097251881Speter  svn_wc_notify_t *notify;
2098251881Speter  svn_wc_notify_action_t action;
2099299742Sdim  const char *notify_path;
2100251881Speter
2101251881Speter  if (! ctx->notify_func2)
2102251881Speter    return SVN_NO_ERROR;
2103251881Speter
2104251881Speter  if (target->skipped)
2105251881Speter    action = svn_wc_notify_skip;
2106251881Speter  else if (target->deleted)
2107251881Speter    action = svn_wc_notify_delete;
2108299742Sdim  else if (target->added || target->replaced || target->move_target_abspath)
2109251881Speter    action = svn_wc_notify_add;
2110251881Speter  else
2111251881Speter    action = svn_wc_notify_patch;
2112251881Speter
2113299742Sdim  if (target->move_target_abspath)
2114299742Sdim    notify_path = target->move_target_abspath;
2115299742Sdim  else
2116299742Sdim    notify_path = target->local_abspath ? target->local_abspath
2117299742Sdim                                        : target->local_relpath;
2118299742Sdim
2119299742Sdim  notify = svn_wc_create_notify(notify_path, action, pool);
2120251881Speter  notify->kind = svn_node_file;
2121251881Speter
2122251881Speter  if (action == svn_wc_notify_skip)
2123251881Speter    {
2124251881Speter      if (target->db_kind == svn_node_none ||
2125251881Speter          target->db_kind == svn_node_unknown)
2126251881Speter        notify->content_state = svn_wc_notify_state_missing;
2127251881Speter      else if (target->db_kind == svn_node_dir)
2128251881Speter        notify->content_state = svn_wc_notify_state_obstructed;
2129251881Speter      else
2130251881Speter        notify->content_state = svn_wc_notify_state_unknown;
2131251881Speter    }
2132251881Speter  else
2133251881Speter    {
2134251881Speter      if (target->had_rejects)
2135251881Speter        notify->content_state = svn_wc_notify_state_conflicted;
2136251881Speter      else if (target->local_mods)
2137251881Speter        notify->content_state = svn_wc_notify_state_merged;
2138251881Speter      else if (target->has_text_changes)
2139251881Speter        notify->content_state = svn_wc_notify_state_changed;
2140251881Speter
2141251881Speter      if (target->had_prop_rejects)
2142251881Speter        notify->prop_state = svn_wc_notify_state_conflicted;
2143251881Speter      else if (target->has_prop_changes)
2144251881Speter        notify->prop_state = svn_wc_notify_state_changed;
2145251881Speter    }
2146251881Speter
2147299742Sdim  ctx->notify_func2(ctx->notify_baton2, notify, pool);
2148251881Speter
2149251881Speter  if (action == svn_wc_notify_patch)
2150251881Speter    {
2151251881Speter      int i;
2152251881Speter      apr_pool_t *iterpool;
2153251881Speter      apr_hash_index_t *hash_index;
2154251881Speter
2155251881Speter      iterpool = svn_pool_create(pool);
2156251881Speter      for (i = 0; i < target->content->hunks->nelts; i++)
2157251881Speter        {
2158251881Speter          const hunk_info_t *hi;
2159251881Speter
2160251881Speter          svn_pool_clear(iterpool);
2161251881Speter
2162251881Speter          hi = APR_ARRAY_IDX(target->content->hunks, i, hunk_info_t *);
2163251881Speter
2164251881Speter          SVN_ERR(send_hunk_notification(hi, target, NULL /* prop_name */,
2165251881Speter                                         ctx, iterpool));
2166251881Speter        }
2167251881Speter
2168251881Speter      for (hash_index = apr_hash_first(pool, target->prop_targets);
2169251881Speter           hash_index;
2170251881Speter           hash_index = apr_hash_next(hash_index))
2171251881Speter        {
2172251881Speter          prop_patch_target_t *prop_target;
2173251881Speter
2174299742Sdim          prop_target = apr_hash_this_val(hash_index);
2175251881Speter
2176251881Speter          for (i = 0; i < prop_target->content->hunks->nelts; i++)
2177251881Speter            {
2178251881Speter              const hunk_info_t *hi;
2179251881Speter
2180251881Speter              svn_pool_clear(iterpool);
2181251881Speter
2182251881Speter              hi = APR_ARRAY_IDX(prop_target->content->hunks, i,
2183251881Speter                                 hunk_info_t *);
2184251881Speter
2185251881Speter              /* Don't notify on the hunk level for added or deleted props. */
2186251881Speter              if (prop_target->operation != svn_diff_op_added &&
2187251881Speter                  prop_target->operation != svn_diff_op_deleted)
2188251881Speter                SVN_ERR(send_hunk_notification(hi, target, prop_target->name,
2189251881Speter                                               ctx, iterpool));
2190251881Speter            }
2191251881Speter        }
2192251881Speter      svn_pool_destroy(iterpool);
2193251881Speter    }
2194251881Speter
2195299742Sdim  if (target->move_target_abspath)
2196299742Sdim    {
2197299742Sdim      /* Notify about deletion of move source. */
2198299742Sdim      notify = svn_wc_create_notify(target->local_abspath,
2199299742Sdim                                    svn_wc_notify_delete, pool);
2200299742Sdim      notify->kind = svn_node_file;
2201299742Sdim      ctx->notify_func2(ctx->notify_baton2, notify, pool);
2202299742Sdim    }
2203299742Sdim
2204251881Speter  return SVN_NO_ERROR;
2205251881Speter}
2206251881Speter
2207289166Speter/* Implements the callback for svn_sort__array.  Puts hunks that match
2208289166Speter   before hunks that do not match, puts hunks that match in order
2209289166Speter   based on postion matched, puts hunks that do not match in order
2210289166Speter   based on original position. */
2211289166Speterstatic int
2212289166Spetersort_matched_hunks(const void *a, const void *b)
2213289166Speter{
2214289166Speter  const hunk_info_t *item1 = *((const hunk_info_t * const *)a);
2215289166Speter  const hunk_info_t *item2 = *((const hunk_info_t * const *)b);
2216289166Speter  svn_boolean_t matched1 = !item1->rejected && !item1->already_applied;
2217289166Speter  svn_boolean_t matched2 = !item2->rejected && !item2->already_applied;
2218289166Speter  svn_linenum_t original1, original2;
2219289166Speter
2220289166Speter  if (matched1 && matched2)
2221289166Speter    {
2222289166Speter      /* Both match so use order matched in file. */
2223289166Speter      if (item1->matched_line > item2->matched_line)
2224289166Speter        return 1;
2225289166Speter      else if (item1->matched_line == item2->matched_line)
2226289166Speter        return 0;
2227289166Speter      else
2228289166Speter        return -1;
2229289166Speter    }
2230289166Speter  else if (matched2)
2231289166Speter    /* Only second matches, put it before first. */
2232289166Speter    return 1;
2233289166Speter  else if (matched1)
2234289166Speter    /* Only first matches, put it before second. */
2235289166Speter    return -1;
2236289166Speter
2237289166Speter  /* Neither matches, sort by original_start. */
2238289166Speter  original1 = svn_diff_hunk_get_original_start(item1->hunk);
2239289166Speter  original2 = svn_diff_hunk_get_original_start(item2->hunk);
2240289166Speter  if (original1 > original2)
2241289166Speter    return 1;
2242289166Speter  else if (original1 == original2)
2243289166Speter    return 0;
2244289166Speter  else
2245289166Speter    return -1;
2246289166Speter}
2247289166Speter
2248289166Speter
2249251881Speter/* Apply a PATCH to a working copy at ABS_WC_PATH and put the result
2250251881Speter * into temporary files, to be installed in the working copy later.
2251251881Speter * Return information about the patch target in *PATCH_TARGET, allocated
2252251881Speter * in RESULT_POOL. Use WC_CTX as the working copy context.
2253251881Speter * STRIP_COUNT specifies the number of leading path components
2254251881Speter * which should be stripped from target paths in the patch.
2255251881Speter * REMOVE_TEMPFILES, PATCH_FUNC, and PATCH_BATON as in svn_client_patch().
2256251881Speter * IGNORE_WHITESPACE tells whether whitespace should be considered when
2257251881Speter * doing the matching.
2258251881Speter * Call cancel CANCEL_FUNC with baton CANCEL_BATON to trigger cancellation.
2259251881Speter * Do temporary allocations in SCRATCH_POOL. */
2260251881Speterstatic svn_error_t *
2261251881Speterapply_one_patch(patch_target_t **patch_target, svn_patch_t *patch,
2262251881Speter                const char *abs_wc_path, svn_wc_context_t *wc_ctx,
2263251881Speter                int strip_count,
2264251881Speter                svn_boolean_t ignore_whitespace,
2265251881Speter                svn_boolean_t remove_tempfiles,
2266251881Speter                svn_cancel_func_t cancel_func,
2267251881Speter                void *cancel_baton,
2268251881Speter                apr_pool_t *result_pool, apr_pool_t *scratch_pool)
2269251881Speter{
2270251881Speter  patch_target_t *target;
2271251881Speter  apr_pool_t *iterpool;
2272251881Speter  int i;
2273251881Speter  static const svn_linenum_t MAX_FUZZ = 2;
2274251881Speter  apr_hash_index_t *hash_index;
2275299742Sdim  svn_linenum_t previous_offset = 0;
2276251881Speter
2277251881Speter  SVN_ERR(init_patch_target(&target, patch, abs_wc_path, wc_ctx, strip_count,
2278251881Speter                            remove_tempfiles, result_pool, scratch_pool));
2279251881Speter  if (target->skipped)
2280251881Speter    {
2281251881Speter      *patch_target = target;
2282251881Speter      return SVN_NO_ERROR;
2283251881Speter    }
2284251881Speter
2285251881Speter  iterpool = svn_pool_create(scratch_pool);
2286251881Speter  /* Match hunks. */
2287251881Speter  for (i = 0; i < patch->hunks->nelts; i++)
2288251881Speter    {
2289251881Speter      svn_diff_hunk_t *hunk;
2290251881Speter      hunk_info_t *hi;
2291251881Speter      svn_linenum_t fuzz = 0;
2292251881Speter
2293251881Speter      svn_pool_clear(iterpool);
2294251881Speter
2295251881Speter      if (cancel_func)
2296251881Speter        SVN_ERR(cancel_func(cancel_baton));
2297251881Speter
2298251881Speter      hunk = APR_ARRAY_IDX(patch->hunks, i, svn_diff_hunk_t *);
2299251881Speter
2300251881Speter      /* Determine the line the hunk should be applied at.
2301251881Speter       * If no match is found initially, try with fuzz. */
2302251881Speter      do
2303251881Speter        {
2304251881Speter          SVN_ERR(get_hunk_info(&hi, target, target->content, hunk, fuzz,
2305299742Sdim                                previous_offset,
2306251881Speter                                ignore_whitespace,
2307251881Speter                                FALSE /* is_prop_hunk */,
2308251881Speter                                cancel_func, cancel_baton,
2309251881Speter                                result_pool, iterpool));
2310251881Speter          fuzz++;
2311251881Speter        }
2312251881Speter      while (hi->rejected && fuzz <= MAX_FUZZ && ! hi->already_applied);
2313251881Speter
2314299742Sdim      if (hi->matched_line)
2315299742Sdim        previous_offset
2316299742Sdim          = hi->matched_line - svn_diff_hunk_get_original_start(hunk);
2317299742Sdim
2318251881Speter      APR_ARRAY_PUSH(target->content->hunks, hunk_info_t *) = hi;
2319251881Speter    }
2320251881Speter
2321289166Speter  /* Hunks are applied in the order determined by the matched line and
2322289166Speter     this may be different from the order of the original lines. */
2323289166Speter  svn_sort__array(target->content->hunks, sort_matched_hunks);
2324289166Speter
2325251881Speter  /* Apply or reject hunks. */
2326251881Speter  for (i = 0; i < target->content->hunks->nelts; i++)
2327251881Speter    {
2328251881Speter      hunk_info_t *hi;
2329251881Speter
2330251881Speter      svn_pool_clear(iterpool);
2331251881Speter
2332251881Speter      if (cancel_func)
2333251881Speter        SVN_ERR(cancel_func(cancel_baton));
2334251881Speter
2335251881Speter      hi = APR_ARRAY_IDX(target->content->hunks, i, hunk_info_t *);
2336251881Speter      if (hi->already_applied)
2337251881Speter        continue;
2338251881Speter      else if (hi->rejected)
2339251881Speter        SVN_ERR(reject_hunk(target, target->content, hi->hunk,
2340251881Speter                            NULL /* prop_name */,
2341251881Speter                            iterpool));
2342251881Speter      else
2343251881Speter        SVN_ERR(apply_hunk(target, target->content, hi,
2344251881Speter                           NULL /* prop_name */,  iterpool));
2345251881Speter    }
2346251881Speter
2347251881Speter  if (target->kind_on_disk == svn_node_file)
2348251881Speter    {
2349251881Speter      /* Copy any remaining lines to target. */
2350251881Speter      SVN_ERR(copy_lines_to_target(target->content, 0, scratch_pool));
2351251881Speter      if (! target->content->eof)
2352251881Speter        {
2353251881Speter          /* We could not copy the entire target file to the temporary file,
2354251881Speter           * and would truncate the target if we copied the temporary file
2355251881Speter           * on top of it. Skip this target. */
2356251881Speter          target->skipped = TRUE;
2357251881Speter        }
2358251881Speter    }
2359251881Speter
2360251881Speter  /* Match property hunks. */
2361251881Speter  for (hash_index = apr_hash_first(scratch_pool, patch->prop_patches);
2362251881Speter       hash_index;
2363251881Speter       hash_index = apr_hash_next(hash_index))
2364251881Speter    {
2365251881Speter      svn_prop_patch_t *prop_patch;
2366251881Speter      const char *prop_name;
2367251881Speter      prop_patch_target_t *prop_target;
2368251881Speter
2369299742Sdim      prop_name = apr_hash_this_key(hash_index);
2370299742Sdim      prop_patch = apr_hash_this_val(hash_index);
2371251881Speter
2372251881Speter      if (! strcmp(prop_name, SVN_PROP_SPECIAL))
2373251881Speter        target->is_special = TRUE;
2374251881Speter
2375251881Speter      /* We'll store matched hunks in prop_content. */
2376251881Speter      prop_target = svn_hash_gets(target->prop_targets, prop_name);
2377251881Speter
2378251881Speter      for (i = 0; i < prop_patch->hunks->nelts; i++)
2379251881Speter        {
2380251881Speter          svn_diff_hunk_t *hunk;
2381251881Speter          hunk_info_t *hi;
2382251881Speter          svn_linenum_t fuzz = 0;
2383251881Speter
2384251881Speter          svn_pool_clear(iterpool);
2385251881Speter
2386251881Speter          if (cancel_func)
2387251881Speter            SVN_ERR(cancel_func(cancel_baton));
2388251881Speter
2389251881Speter          hunk = APR_ARRAY_IDX(prop_patch->hunks, i, svn_diff_hunk_t *);
2390251881Speter
2391251881Speter          /* Determine the line the hunk should be applied at.
2392251881Speter           * If no match is found initially, try with fuzz. */
2393251881Speter          do
2394251881Speter            {
2395251881Speter              SVN_ERR(get_hunk_info(&hi, target, prop_target->content,
2396299742Sdim                                    hunk, fuzz, 0,
2397251881Speter                                    ignore_whitespace,
2398251881Speter                                    TRUE /* is_prop_hunk */,
2399251881Speter                                    cancel_func, cancel_baton,
2400251881Speter                                    result_pool, iterpool));
2401251881Speter              fuzz++;
2402251881Speter            }
2403251881Speter          while (hi->rejected && fuzz <= MAX_FUZZ && ! hi->already_applied);
2404251881Speter
2405251881Speter          APR_ARRAY_PUSH(prop_target->content->hunks, hunk_info_t *) = hi;
2406251881Speter        }
2407251881Speter    }
2408251881Speter
2409251881Speter  /* Apply or reject property hunks. */
2410251881Speter  for (hash_index = apr_hash_first(scratch_pool, target->prop_targets);
2411251881Speter       hash_index;
2412251881Speter       hash_index = apr_hash_next(hash_index))
2413251881Speter    {
2414251881Speter      prop_patch_target_t *prop_target;
2415251881Speter
2416299742Sdim      prop_target = apr_hash_this_val(hash_index);
2417251881Speter
2418251881Speter      for (i = 0; i < prop_target->content->hunks->nelts; i++)
2419251881Speter        {
2420251881Speter          hunk_info_t *hi;
2421251881Speter
2422251881Speter          svn_pool_clear(iterpool);
2423251881Speter
2424251881Speter          hi = APR_ARRAY_IDX(prop_target->content->hunks, i,
2425251881Speter                             hunk_info_t *);
2426251881Speter          if (hi->already_applied)
2427251881Speter            continue;
2428251881Speter          else if (hi->rejected)
2429251881Speter            SVN_ERR(reject_hunk(target, prop_target->content, hi->hunk,
2430251881Speter                                prop_target->name,
2431251881Speter                                iterpool));
2432251881Speter          else
2433251881Speter            SVN_ERR(apply_hunk(target, prop_target->content, hi,
2434251881Speter                               prop_target->name,
2435251881Speter                               iterpool));
2436251881Speter        }
2437251881Speter
2438251881Speter        if (prop_target->content->existed)
2439251881Speter          {
2440251881Speter            /* Copy any remaining lines to target. */
2441251881Speter            SVN_ERR(copy_lines_to_target(prop_target->content, 0,
2442251881Speter                                         scratch_pool));
2443251881Speter            if (! prop_target->content->eof)
2444251881Speter              {
2445251881Speter                /* We could not copy the entire target property to the
2446251881Speter                 * temporary file, and would truncate the target if we
2447251881Speter                 * copied the temporary file on top of it. Skip this target.  */
2448251881Speter                target->skipped = TRUE;
2449251881Speter              }
2450251881Speter          }
2451251881Speter      }
2452251881Speter
2453251881Speter  svn_pool_destroy(iterpool);
2454251881Speter
2455251881Speter  if (!target->is_symlink)
2456251881Speter    {
2457251881Speter      /* Now close files we don't need any longer to get their contents
2458251881Speter       * flushed to disk.
2459251881Speter       * But we're not closing the reject file -- it still needed and
2460251881Speter       * will be closed later in write_out_rejected_hunks(). */
2461251881Speter      if (target->kind_on_disk == svn_node_file)
2462251881Speter        SVN_ERR(svn_io_file_close(target->file, scratch_pool));
2463251881Speter
2464251881Speter      SVN_ERR(svn_io_file_close(target->patched_file, scratch_pool));
2465251881Speter    }
2466251881Speter
2467251881Speter  if (! target->skipped)
2468251881Speter    {
2469251881Speter      apr_finfo_t working_file;
2470251881Speter      apr_finfo_t patched_file;
2471251881Speter
2472251881Speter      /* Get sizes of the patched temporary file and the working file.
2473251881Speter       * We'll need those to figure out whether we should delete the
2474251881Speter       * patched file. */
2475251881Speter      SVN_ERR(svn_io_stat(&patched_file, target->patched_path,
2476251881Speter                          APR_FINFO_SIZE | APR_FINFO_LINK, scratch_pool));
2477251881Speter      if (target->kind_on_disk == svn_node_file)
2478251881Speter        SVN_ERR(svn_io_stat(&working_file, target->local_abspath,
2479251881Speter                            APR_FINFO_SIZE | APR_FINFO_LINK, scratch_pool));
2480251881Speter      else
2481251881Speter        working_file.size = 0;
2482251881Speter
2483251881Speter      if (patched_file.size == 0 && working_file.size > 0)
2484251881Speter        {
2485251881Speter          /* If a unidiff removes all lines from a file, that usually
2486251881Speter           * means deletion, so we can confidently schedule the target
2487251881Speter           * for deletion. In the rare case where the unidiff was really
2488251881Speter           * meant to replace a file with an empty one, this may not
2489251881Speter           * be desirable. But the deletion can easily be reverted and
2490251881Speter           * creating an empty file manually is not exactly hard either. */
2491251881Speter          target->deleted = (target->db_kind == svn_node_file);
2492251881Speter        }
2493251881Speter      else if (patched_file.size == 0 && working_file.size == 0)
2494251881Speter        {
2495251881Speter          /* The target was empty or non-existent to begin with
2496251881Speter           * and no content was changed by patching.
2497251881Speter           * Report this as skipped if it didn't exist, unless in the special
2498251881Speter           * case of adding an empty file which has properties set on it or
2499251881Speter           * adding an empty file with a 'git diff' */
2500251881Speter          if (target->kind_on_disk == svn_node_none
2501251881Speter              && ! target->has_prop_changes
2502251881Speter              && ! target->added)
2503251881Speter            target->skipped = TRUE;
2504251881Speter        }
2505251881Speter      else if (patched_file.size > 0 && working_file.size == 0)
2506251881Speter        {
2507251881Speter          /* The patch has created a file. */
2508251881Speter          if (target->locally_deleted)
2509251881Speter            target->replaced = TRUE;
2510251881Speter          else if (target->db_kind == svn_node_none)
2511251881Speter            target->added = TRUE;
2512251881Speter        }
2513251881Speter    }
2514251881Speter
2515251881Speter  *patch_target = target;
2516251881Speter
2517251881Speter  return SVN_NO_ERROR;
2518251881Speter}
2519251881Speter
2520251881Speter/* Try to create missing parent directories for TARGET in the working copy
2521251881Speter * rooted at ABS_WC_PATH, and add the parents to version control.
2522251881Speter * If the parents cannot be created, mark the target as skipped.
2523251881Speter * Use client context CTX. If DRY_RUN is true, do not create missing
2524251881Speter * parents but issue notifications only.
2525251881Speter * Use SCRATCH_POOL for temporary allocations. */
2526251881Speterstatic svn_error_t *
2527251881Spetercreate_missing_parents(patch_target_t *target,
2528251881Speter                       const char *abs_wc_path,
2529251881Speter                       svn_client_ctx_t *ctx,
2530251881Speter                       svn_boolean_t dry_run,
2531251881Speter                       apr_pool_t *scratch_pool)
2532251881Speter{
2533251881Speter  const char *local_abspath;
2534251881Speter  apr_array_header_t *components;
2535251881Speter  int present_components;
2536251881Speter  int i;
2537251881Speter  apr_pool_t *iterpool;
2538251881Speter
2539251881Speter  /* Check if we can safely create the target's parent. */
2540251881Speter  local_abspath = abs_wc_path;
2541251881Speter  components = svn_path_decompose(target->local_relpath, scratch_pool);
2542251881Speter  present_components = 0;
2543251881Speter  iterpool = svn_pool_create(scratch_pool);
2544251881Speter  for (i = 0; i < components->nelts - 1; i++)
2545251881Speter    {
2546251881Speter      const char *component;
2547251881Speter      svn_node_kind_t wc_kind, disk_kind;
2548251881Speter
2549251881Speter      svn_pool_clear(iterpool);
2550251881Speter
2551251881Speter      component = APR_ARRAY_IDX(components, i, const char *);
2552251881Speter      local_abspath = svn_dirent_join(local_abspath, component, scratch_pool);
2553251881Speter
2554251881Speter      SVN_ERR(svn_wc_read_kind2(&wc_kind, ctx->wc_ctx, local_abspath,
2555251881Speter                                FALSE, TRUE, iterpool));
2556251881Speter
2557251881Speter      SVN_ERR(svn_io_check_path(local_abspath, &disk_kind, iterpool));
2558251881Speter
2559251881Speter      if (disk_kind == svn_node_file || wc_kind == svn_node_file)
2560251881Speter        {
2561251881Speter          /* on-disk files and missing files are obstructions */
2562251881Speter          target->skipped = TRUE;
2563251881Speter          break;
2564251881Speter        }
2565251881Speter      else if (disk_kind == svn_node_dir)
2566251881Speter        {
2567251881Speter          if (wc_kind == svn_node_dir)
2568251881Speter            present_components++;
2569251881Speter          else
2570251881Speter            {
2571251881Speter              target->skipped = TRUE;
2572251881Speter              break;
2573251881Speter            }
2574251881Speter        }
2575251881Speter      else if (wc_kind != svn_node_none)
2576251881Speter        {
2577251881Speter          /* Node is missing */
2578251881Speter          target->skipped = TRUE;
2579251881Speter          break;
2580251881Speter        }
2581251881Speter      else
2582251881Speter        {
2583251881Speter          /* It's not a file, it's not a dir...
2584251881Speter             Let's add a dir */
2585251881Speter          break;
2586251881Speter        }
2587251881Speter    }
2588251881Speter  if (! target->skipped)
2589251881Speter    {
2590251881Speter      local_abspath = abs_wc_path;
2591251881Speter      for (i = 0; i < present_components; i++)
2592251881Speter        {
2593251881Speter          const char *component;
2594251881Speter          component = APR_ARRAY_IDX(components, i, const char *);
2595251881Speter          local_abspath = svn_dirent_join(local_abspath,
2596251881Speter                                          component, scratch_pool);
2597251881Speter        }
2598251881Speter
2599251881Speter      if (!dry_run && present_components < components->nelts - 1)
2600251881Speter        SVN_ERR(svn_io_make_dir_recursively(
2601251881Speter                        svn_dirent_join(
2602251881Speter                                   abs_wc_path,
2603251881Speter                                   svn_relpath_dirname(target->local_relpath,
2604251881Speter                                                       scratch_pool),
2605251881Speter                                   scratch_pool),
2606251881Speter                        scratch_pool));
2607251881Speter
2608251881Speter      for (i = present_components; i < components->nelts - 1; i++)
2609251881Speter        {
2610251881Speter          const char *component;
2611251881Speter
2612251881Speter          svn_pool_clear(iterpool);
2613251881Speter
2614251881Speter          component = APR_ARRAY_IDX(components, i, const char *);
2615251881Speter          local_abspath = svn_dirent_join(local_abspath, component,
2616251881Speter                                          scratch_pool);
2617251881Speter          if (dry_run)
2618251881Speter            {
2619251881Speter              if (ctx->notify_func2)
2620251881Speter                {
2621251881Speter                  /* Just do notification. */
2622251881Speter                  svn_wc_notify_t *notify;
2623251881Speter                  notify = svn_wc_create_notify(local_abspath,
2624251881Speter                                                svn_wc_notify_add,
2625251881Speter                                                iterpool);
2626251881Speter                  notify->kind = svn_node_dir;
2627251881Speter                  ctx->notify_func2(ctx->notify_baton2, notify,
2628251881Speter                                    iterpool);
2629251881Speter                }
2630251881Speter            }
2631251881Speter          else
2632251881Speter            {
2633251881Speter              /* Create the missing component and add it
2634251881Speter               * to version control. Allow cancellation since we
2635251881Speter               * have not modified the working copy yet for this
2636251881Speter               * target. */
2637251881Speter
2638251881Speter              if (ctx->cancel_func)
2639251881Speter                SVN_ERR(ctx->cancel_func(ctx->cancel_baton));
2640251881Speter
2641299742Sdim              SVN_ERR(svn_wc_add_from_disk3(ctx->wc_ctx, local_abspath,
2642251881Speter                                            NULL /*props*/,
2643299742Sdim                                            FALSE /* skip checks */,
2644251881Speter                                            ctx->notify_func2, ctx->notify_baton2,
2645251881Speter                                            iterpool));
2646251881Speter            }
2647251881Speter        }
2648251881Speter    }
2649251881Speter
2650251881Speter  svn_pool_destroy(iterpool);
2651251881Speter  return SVN_NO_ERROR;
2652251881Speter}
2653251881Speter
2654251881Speter/* Install a patched TARGET into the working copy at ABS_WC_PATH.
2655251881Speter * Use client context CTX to retrieve WC_CTX, and possibly doing
2656251881Speter * notifications. If DRY_RUN is TRUE, don't modify the working copy.
2657251881Speter * Do temporary allocations in POOL. */
2658251881Speterstatic svn_error_t *
2659251881Speterinstall_patched_target(patch_target_t *target, const char *abs_wc_path,
2660251881Speter                       svn_client_ctx_t *ctx, svn_boolean_t dry_run,
2661251881Speter                       apr_pool_t *pool)
2662251881Speter{
2663251881Speter  if (target->deleted)
2664251881Speter    {
2665251881Speter      if (! dry_run)
2666251881Speter        {
2667251881Speter          /* Schedule the target for deletion.  Suppress
2668251881Speter           * notification, we'll do it manually in a minute
2669251881Speter           * because we also need to notify during dry-run.
2670251881Speter           * Also suppress cancellation, because we'd rather
2671251881Speter           * notify about what we did before aborting. */
2672251881Speter          SVN_ERR(svn_wc_delete4(ctx->wc_ctx, target->local_abspath,
2673251881Speter                                 FALSE /* keep_local */, FALSE,
2674251881Speter                                 NULL, NULL, NULL, NULL, pool));
2675251881Speter        }
2676251881Speter    }
2677251881Speter  else
2678251881Speter    {
2679251881Speter      svn_node_kind_t parent_db_kind;
2680251881Speter      if (target->added || target->replaced)
2681251881Speter        {
2682251881Speter          const char *parent_abspath;
2683251881Speter
2684251881Speter          parent_abspath = svn_dirent_dirname(target->local_abspath,
2685251881Speter                                              pool);
2686251881Speter          /* If the target's parent directory does not yet exist
2687251881Speter           * we need to create it before we can copy the patched
2688251881Speter           * result in place. */
2689251881Speter          SVN_ERR(svn_wc_read_kind2(&parent_db_kind, ctx->wc_ctx,
2690251881Speter                                    parent_abspath, FALSE, FALSE, pool));
2691251881Speter
2692251881Speter          /* We can't add targets under nodes scheduled for delete, so add
2693251881Speter             a new directory if needed. */
2694251881Speter          if (parent_db_kind == svn_node_dir
2695251881Speter              || parent_db_kind == svn_node_file)
2696251881Speter            {
2697251881Speter              if (parent_db_kind != svn_node_dir)
2698251881Speter                target->skipped = TRUE;
2699251881Speter              else
2700251881Speter                {
2701251881Speter                  svn_node_kind_t disk_kind;
2702251881Speter
2703251881Speter                  SVN_ERR(svn_io_check_path(parent_abspath, &disk_kind, pool));
2704251881Speter                  if (disk_kind != svn_node_dir)
2705251881Speter                    target->skipped = TRUE;
2706251881Speter                }
2707251881Speter            }
2708251881Speter          else
2709251881Speter            SVN_ERR(create_missing_parents(target, abs_wc_path, ctx,
2710251881Speter                                           dry_run, pool));
2711251881Speter
2712251881Speter        }
2713251881Speter      else
2714251881Speter        {
2715251881Speter          svn_node_kind_t wc_kind;
2716251881Speter
2717251881Speter          /* The target should exist */
2718251881Speter          SVN_ERR(svn_wc_read_kind2(&wc_kind, ctx->wc_ctx,
2719251881Speter                                    target->local_abspath,
2720251881Speter                                    FALSE, FALSE, pool));
2721251881Speter
2722251881Speter          if (target->kind_on_disk == svn_node_none
2723251881Speter              || wc_kind != target->kind_on_disk)
2724251881Speter            {
2725251881Speter              target->skipped = TRUE;
2726251881Speter            }
2727251881Speter        }
2728251881Speter
2729251881Speter      if (! dry_run && ! target->skipped)
2730251881Speter        {
2731251881Speter          if (target->is_special)
2732251881Speter            {
2733251881Speter              svn_stream_t *stream;
2734251881Speter              svn_stream_t *patched_stream;
2735251881Speter
2736251881Speter              SVN_ERR(svn_stream_open_readonly(&patched_stream,
2737251881Speter                                               target->patched_path,
2738251881Speter                                               pool, pool));
2739251881Speter              SVN_ERR(svn_subst_create_specialfile(&stream,
2740251881Speter                                                   target->local_abspath,
2741251881Speter                                                   pool, pool));
2742251881Speter              SVN_ERR(svn_stream_copy3(patched_stream, stream,
2743251881Speter                                       ctx->cancel_func, ctx->cancel_baton,
2744251881Speter                                       pool));
2745251881Speter            }
2746251881Speter          else
2747251881Speter            {
2748251881Speter              svn_boolean_t repair_eol;
2749251881Speter
2750251881Speter              /* Copy the patched file on top of the target file.
2751251881Speter               * Always expand keywords in the patched file, but repair EOL
2752251881Speter               * only if svn:eol-style dictates a particular style. */
2753251881Speter              repair_eol = (target->content->eol_style ==
2754251881Speter                              svn_subst_eol_style_fixed ||
2755251881Speter                            target->content->eol_style ==
2756251881Speter                              svn_subst_eol_style_native);
2757251881Speter
2758251881Speter              SVN_ERR(svn_subst_copy_and_translate4(
2759299742Sdim                        target->patched_path,
2760299742Sdim                        target->move_target_abspath
2761299742Sdim                          ? target->move_target_abspath
2762299742Sdim                          : target->local_abspath,
2763251881Speter                        target->content->eol_str, repair_eol,
2764251881Speter                        target->content->keywords,
2765251881Speter                        TRUE /* expand */, FALSE /* special */,
2766251881Speter                        ctx->cancel_func, ctx->cancel_baton, pool));
2767251881Speter            }
2768251881Speter
2769251881Speter          if (target->added || target->replaced)
2770251881Speter            {
2771251881Speter              /* The target file didn't exist previously,
2772251881Speter               * so add it to version control.
2773251881Speter               * Suppress notification, we'll do that later (and also
2774251881Speter               * during dry-run). Don't allow cancellation because
2775251881Speter               * we'd rather notify about what we did before aborting. */
2776299742Sdim              SVN_ERR(svn_wc_add_from_disk3(ctx->wc_ctx, target->local_abspath,
2777251881Speter                                            NULL /*props*/,
2778299742Sdim                                            FALSE /* skip checks */,
2779251881Speter                                            NULL, NULL, pool));
2780251881Speter            }
2781251881Speter
2782251881Speter          /* Restore the target's executable bit if necessary. */
2783299742Sdim          SVN_ERR(svn_io_set_file_executable(target->move_target_abspath
2784299742Sdim                                               ? target->move_target_abspath
2785299742Sdim                                               : target->local_abspath,
2786251881Speter                                             target->executable,
2787251881Speter                                             FALSE, pool));
2788299742Sdim
2789299742Sdim          if (target->move_target_abspath)
2790299742Sdim            {
2791299742Sdim              /* ### Copying the patched content to the move target location,
2792299742Sdim               * performing the move in meta-data, and removing the file at
2793299742Sdim               * the move source should be one atomic operation. */
2794299742Sdim
2795299742Sdim              /* ### Create missing parents. */
2796299742Sdim
2797299742Sdim              /* Perform the move in meta-data. */
2798299742Sdim              SVN_ERR(svn_wc__move2(ctx->wc_ctx,
2799299742Sdim                                    target->local_abspath,
2800299742Sdim                                    target->move_target_abspath,
2801299742Sdim                                    TRUE, /* metadata_only */
2802299742Sdim                                    FALSE, /* allow_mixed_revisions */
2803299742Sdim                                    NULL, NULL, NULL, NULL,
2804299742Sdim                                    pool));
2805299742Sdim
2806299742Sdim              /* Delete the patch target's old location from disk. */
2807299742Sdim              SVN_ERR(svn_io_remove_file2(target->local_abspath, FALSE, pool));
2808299742Sdim            }
2809251881Speter        }
2810251881Speter    }
2811251881Speter
2812251881Speter  return SVN_NO_ERROR;
2813251881Speter}
2814251881Speter
2815251881Speter/* Write out rejected hunks, if any, to TARGET->REJECT_PATH. If DRY_RUN is
2816251881Speter * TRUE, don't modify the working copy.
2817251881Speter * Do temporary allocations in POOL.
2818251881Speter */
2819251881Speterstatic svn_error_t *
2820251881Speterwrite_out_rejected_hunks(patch_target_t *target,
2821251881Speter                         svn_boolean_t dry_run,
2822251881Speter                         apr_pool_t *pool)
2823251881Speter{
2824251881Speter  SVN_ERR(svn_io_file_close(target->reject_file, pool));
2825251881Speter
2826251881Speter  if (! dry_run && (target->had_rejects || target->had_prop_rejects))
2827251881Speter    {
2828251881Speter      /* Write out rejected hunks, if any. */
2829251881Speter      SVN_ERR(svn_io_copy_file(target->reject_path,
2830251881Speter                               apr_psprintf(pool, "%s.svnpatch.rej",
2831251881Speter                               target->local_abspath),
2832251881Speter                               FALSE, pool));
2833251881Speter      /* ### TODO mark file as conflicted. */
2834251881Speter    }
2835251881Speter  return SVN_NO_ERROR;
2836251881Speter}
2837251881Speter
2838251881Speter/* Install the patched properties for TARGET. Use client context CTX to
2839251881Speter * retrieve WC_CTX. If DRY_RUN is TRUE, don't modify the working copy.
2840251881Speter * Do temporary allocations in SCRATCH_POOL. */
2841251881Speterstatic svn_error_t *
2842251881Speterinstall_patched_prop_targets(patch_target_t *target,
2843251881Speter                             svn_client_ctx_t *ctx, svn_boolean_t dry_run,
2844251881Speter                             apr_pool_t *scratch_pool)
2845251881Speter{
2846251881Speter  apr_hash_index_t *hi;
2847251881Speter  apr_pool_t *iterpool;
2848251881Speter
2849251881Speter  iterpool = svn_pool_create(scratch_pool);
2850251881Speter
2851251881Speter  for (hi = apr_hash_first(scratch_pool, target->prop_targets);
2852251881Speter       hi;
2853251881Speter       hi = apr_hash_next(hi))
2854251881Speter    {
2855299742Sdim      prop_patch_target_t *prop_target = apr_hash_this_val(hi);
2856251881Speter      const svn_string_t *prop_val;
2857251881Speter      svn_error_t *err;
2858251881Speter
2859251881Speter      svn_pool_clear(iterpool);
2860251881Speter
2861251881Speter      if (ctx->cancel_func)
2862251881Speter        SVN_ERR(ctx->cancel_func(ctx->cancel_baton));
2863251881Speter
2864251881Speter      /* For a deleted prop we only set the value to NULL. */
2865251881Speter      if (prop_target->operation == svn_diff_op_deleted)
2866251881Speter        {
2867251881Speter          if (! dry_run)
2868251881Speter            SVN_ERR(svn_wc_prop_set4(ctx->wc_ctx, target->local_abspath,
2869251881Speter                                     prop_target->name, NULL, svn_depth_empty,
2870251881Speter                                     TRUE /* skip_checks */,
2871251881Speter                                     NULL /* changelist_filter */,
2872251881Speter                                     NULL, NULL /* cancellation */,
2873251881Speter                                     NULL, NULL /* notification */,
2874251881Speter                                     iterpool));
2875251881Speter          continue;
2876251881Speter        }
2877251881Speter
2878251881Speter      /* If the patch target doesn't exist yet, the patch wants to add an
2879251881Speter       * empty file with properties set on it. So create an empty file and
2880251881Speter       * add it to version control. But if the patch was in the 'git format'
2881251881Speter       * then the file has already been added.
2882251881Speter       *
2883251881Speter       * ### How can we tell whether the patch really wanted to create
2884251881Speter       * ### an empty directory? */
2885251881Speter      if (! target->has_text_changes
2886251881Speter          && target->kind_on_disk == svn_node_none
2887251881Speter          && ! target->added)
2888251881Speter        {
2889251881Speter          if (! dry_run)
2890251881Speter            {
2891299742Sdim              SVN_ERR(svn_io_file_create_empty(target->local_abspath,
2892299742Sdim                                               scratch_pool));
2893299742Sdim              SVN_ERR(svn_wc_add_from_disk3(ctx->wc_ctx, target->local_abspath,
2894251881Speter                                            NULL /*props*/,
2895299742Sdim                                            FALSE /* skip checks */,
2896251881Speter                                            /* suppress notification */
2897251881Speter                                            NULL, NULL,
2898251881Speter                                            iterpool));
2899251881Speter            }
2900251881Speter          target->added = TRUE;
2901251881Speter        }
2902251881Speter
2903251881Speter      /* Attempt to set the property, and reject all hunks if this
2904251881Speter         fails.  If the property had a non-empty value, but now has
2905251881Speter         an empty one, we'll just delete the property altogether.  */
2906251881Speter      if (prop_target->value && prop_target->value->len
2907251881Speter          && prop_target->patched_value && !prop_target->patched_value->len)
2908251881Speter        prop_val = NULL;
2909251881Speter      else
2910251881Speter        prop_val = svn_stringbuf__morph_into_string(prop_target->patched_value);
2911251881Speter
2912251881Speter      if (dry_run)
2913251881Speter        {
2914251881Speter          const svn_string_t *canon_propval;
2915251881Speter
2916251881Speter          err = svn_wc_canonicalize_svn_prop(&canon_propval,
2917251881Speter                                             prop_target->name,
2918251881Speter                                             prop_val, target->local_abspath,
2919251881Speter                                             target->db_kind,
2920251881Speter                                             TRUE, /* ### Skipping checks */
2921251881Speter                                             NULL, NULL,
2922251881Speter                                             iterpool);
2923251881Speter        }
2924251881Speter      else
2925251881Speter        {
2926251881Speter          err = svn_wc_prop_set4(ctx->wc_ctx, target->local_abspath,
2927251881Speter                                 prop_target->name, prop_val, svn_depth_empty,
2928251881Speter                                 TRUE /* skip_checks */,
2929251881Speter                                 NULL /* changelist_filter */,
2930251881Speter                                 NULL, NULL /* cancellation */,
2931251881Speter                                 NULL, NULL /* notification */,
2932251881Speter                                 iterpool);
2933251881Speter        }
2934251881Speter
2935251881Speter      if (err)
2936251881Speter        {
2937251881Speter          /* ### The errors which svn_wc_canonicalize_svn_prop() will
2938251881Speter           * ### return aren't documented. */
2939251881Speter          if (err->apr_err == SVN_ERR_ILLEGAL_TARGET ||
2940251881Speter              err->apr_err == SVN_ERR_NODE_UNEXPECTED_KIND ||
2941251881Speter              err->apr_err == SVN_ERR_IO_UNKNOWN_EOL ||
2942251881Speter              err->apr_err == SVN_ERR_BAD_MIME_TYPE ||
2943251881Speter              err->apr_err == SVN_ERR_CLIENT_INVALID_EXTERNALS_DESCRIPTION)
2944251881Speter            {
2945251881Speter              int i;
2946251881Speter
2947251881Speter              svn_error_clear(err);
2948251881Speter
2949251881Speter              for (i = 0; i < prop_target->content->hunks->nelts; i++)
2950251881Speter                {
2951251881Speter                  hunk_info_t *hunk_info;
2952251881Speter
2953251881Speter                  hunk_info = APR_ARRAY_IDX(prop_target->content->hunks,
2954251881Speter                                            i, hunk_info_t *);
2955251881Speter                  hunk_info->rejected = TRUE;
2956251881Speter                  SVN_ERR(reject_hunk(target, prop_target->content,
2957251881Speter                                      hunk_info->hunk, prop_target->name,
2958251881Speter                                      iterpool));
2959251881Speter                }
2960251881Speter            }
2961251881Speter          else
2962251881Speter            return svn_error_trace(err);
2963251881Speter        }
2964251881Speter
2965251881Speter    }
2966251881Speter
2967251881Speter  svn_pool_destroy(iterpool);
2968251881Speter
2969251881Speter  return SVN_NO_ERROR;
2970251881Speter}
2971251881Speter
2972251881Speter/* Baton for can_delete_callback */
2973251881Speterstruct can_delete_baton_t
2974251881Speter{
2975251881Speter  svn_boolean_t must_keep;
2976251881Speter  const apr_array_header_t *targets_info;
2977251881Speter  const char *local_abspath;
2978251881Speter};
2979251881Speter
2980251881Speter/* Implements svn_wc_status_func4_t. */
2981251881Speterstatic svn_error_t *
2982251881Spetercan_delete_callback(void *baton,
2983251881Speter                    const char *abspath,
2984251881Speter                    const svn_wc_status3_t *status,
2985251881Speter                    apr_pool_t *pool)
2986251881Speter{
2987251881Speter  struct can_delete_baton_t *cb = baton;
2988251881Speter  int i;
2989251881Speter
2990251881Speter  switch(status->node_status)
2991251881Speter    {
2992251881Speter      case svn_wc_status_none:
2993251881Speter      case svn_wc_status_deleted:
2994251881Speter        return SVN_NO_ERROR;
2995251881Speter
2996251881Speter      default:
2997251881Speter        if (! strcmp(cb->local_abspath, abspath))
2998251881Speter          return SVN_NO_ERROR; /* Only interested in descendants */
2999251881Speter
3000251881Speter        for (i = 0; i < cb->targets_info->nelts; i++)
3001251881Speter          {
3002251881Speter            const patch_target_info_t *target_info =
3003251881Speter               APR_ARRAY_IDX(cb->targets_info, i, const patch_target_info_t *);
3004251881Speter
3005251881Speter            if (! strcmp(target_info->local_abspath, abspath))
3006251881Speter              {
3007251881Speter                if (target_info->deleted)
3008251881Speter                  return SVN_NO_ERROR;
3009251881Speter
3010251881Speter                break; /* Cease invocation; must keep */
3011251881Speter              }
3012251881Speter          }
3013251881Speter
3014251881Speter        cb->must_keep = TRUE;
3015251881Speter
3016251881Speter        return svn_error_create(SVN_ERR_CEASE_INVOCATION, NULL, NULL);
3017251881Speter    }
3018251881Speter}
3019251881Speter
3020251881Speterstatic svn_error_t *
3021251881Spetercheck_ancestor_delete(const char *deleted_target,
3022251881Speter                      apr_array_header_t *targets_info,
3023251881Speter                      const char *apply_root,
3024251881Speter                      svn_boolean_t dry_run,
3025251881Speter                      svn_client_ctx_t *ctx,
3026251881Speter                      apr_pool_t *result_pool,
3027251881Speter                      apr_pool_t *scratch_pool)
3028251881Speter{
3029251881Speter  struct can_delete_baton_t cb;
3030251881Speter  svn_error_t *err;
3031299742Sdim  apr_array_header_t *ignores;
3032251881Speter  apr_pool_t *iterpool = svn_pool_create(scratch_pool);
3033251881Speter
3034251881Speter  const char *dir_abspath = svn_dirent_dirname(deleted_target, scratch_pool);
3035251881Speter
3036299742Sdim  SVN_ERR(svn_wc_get_default_ignores(&ignores, ctx->config, scratch_pool));
3037299742Sdim
3038251881Speter  while (svn_dirent_is_child(apply_root, dir_abspath, iterpool))
3039251881Speter    {
3040251881Speter      svn_pool_clear(iterpool);
3041251881Speter
3042251881Speter      cb.local_abspath = dir_abspath;
3043251881Speter      cb.must_keep = FALSE;
3044251881Speter      cb.targets_info = targets_info;
3045251881Speter
3046251881Speter      err = svn_wc_walk_status(ctx->wc_ctx, dir_abspath, svn_depth_infinity,
3047299742Sdim                               TRUE, FALSE, FALSE, ignores,
3048251881Speter                               can_delete_callback, &cb,
3049251881Speter                               ctx->cancel_func, ctx->cancel_baton,
3050251881Speter                               iterpool);
3051251881Speter
3052251881Speter      if (err)
3053251881Speter        {
3054251881Speter          if (err->apr_err != SVN_ERR_CEASE_INVOCATION)
3055251881Speter            return svn_error_trace(err);
3056251881Speter
3057251881Speter          svn_error_clear(err);
3058251881Speter        }
3059251881Speter
3060251881Speter      if (cb.must_keep)
3061251881Speter      {
3062251881Speter        break;
3063251881Speter      }
3064251881Speter
3065251881Speter      if (! dry_run)
3066251881Speter        {
3067251881Speter          SVN_ERR(svn_wc_delete4(ctx->wc_ctx, dir_abspath, FALSE, FALSE,
3068251881Speter                                 ctx->cancel_func, ctx->cancel_baton,
3069251881Speter                                 NULL, NULL,
3070251881Speter                                 scratch_pool));
3071251881Speter        }
3072251881Speter
3073251881Speter      {
3074251881Speter        patch_target_info_t *pti = apr_pcalloc(result_pool, sizeof(*pti));
3075251881Speter
3076251881Speter        pti->local_abspath = apr_pstrdup(result_pool, dir_abspath);
3077251881Speter        pti->deleted = TRUE;
3078251881Speter
3079251881Speter        APR_ARRAY_PUSH(targets_info, patch_target_info_t *) = pti;
3080251881Speter      }
3081251881Speter
3082251881Speter
3083251881Speter      if (ctx->notify_func2)
3084251881Speter        {
3085251881Speter          svn_wc_notify_t *notify;
3086251881Speter
3087251881Speter          notify = svn_wc_create_notify(dir_abspath, svn_wc_notify_delete,
3088251881Speter                                    iterpool);
3089251881Speter          notify->kind = svn_node_dir;
3090251881Speter
3091251881Speter          ctx->notify_func2(ctx->notify_baton2, notify, iterpool);
3092251881Speter        }
3093251881Speter
3094251881Speter      /* And check if we must also delete the parent */
3095251881Speter      dir_abspath = svn_dirent_dirname(dir_abspath, scratch_pool);
3096251881Speter    }
3097251881Speter
3098251881Speter  svn_pool_destroy(iterpool);
3099251881Speter
3100251881Speter  return SVN_NO_ERROR;
3101251881Speter}
3102251881Speter
3103251881Speter/* This function is the main entry point into the patch code. */
3104251881Speterstatic svn_error_t *
3105251881Speterapply_patches(/* The path to the patch file. */
3106251881Speter              const char *patch_abspath,
3107251881Speter              /* The abspath to the working copy the patch should be applied to. */
3108251881Speter              const char *abs_wc_path,
3109251881Speter              /* Indicates whether we're doing a dry run. */
3110251881Speter              svn_boolean_t dry_run,
3111251881Speter              /* Number of leading components to strip from patch target paths. */
3112251881Speter              int strip_count,
3113251881Speter              /* Whether to apply the patch in reverse. */
3114251881Speter              svn_boolean_t reverse,
3115251881Speter              /* Whether to ignore whitespace when matching context lines. */
3116251881Speter              svn_boolean_t ignore_whitespace,
3117251881Speter              /* As in svn_client_patch(). */
3118251881Speter              svn_boolean_t remove_tempfiles,
3119251881Speter              /* As in svn_client_patch(). */
3120251881Speter              svn_client_patch_func_t patch_func,
3121251881Speter              void *patch_baton,
3122251881Speter              /* The client context. */
3123251881Speter              svn_client_ctx_t *ctx,
3124251881Speter              apr_pool_t *scratch_pool)
3125251881Speter{
3126251881Speter  svn_patch_t *patch;
3127251881Speter  apr_pool_t *iterpool;
3128251881Speter  svn_patch_file_t *patch_file;
3129251881Speter  apr_array_header_t *targets_info;
3130251881Speter
3131251881Speter  /* Try to open the patch file. */
3132251881Speter  SVN_ERR(svn_diff_open_patch_file(&patch_file, patch_abspath, scratch_pool));
3133251881Speter
3134251881Speter  /* Apply patches. */
3135251881Speter  targets_info = apr_array_make(scratch_pool, 0,
3136251881Speter                                sizeof(patch_target_info_t *));
3137251881Speter  iterpool = svn_pool_create(scratch_pool);
3138251881Speter  do
3139251881Speter    {
3140251881Speter      svn_pool_clear(iterpool);
3141251881Speter
3142251881Speter      if (ctx->cancel_func)
3143251881Speter        SVN_ERR(ctx->cancel_func(ctx->cancel_baton));
3144251881Speter
3145251881Speter      SVN_ERR(svn_diff_parse_next_patch(&patch, patch_file,
3146251881Speter                                        reverse, ignore_whitespace,
3147251881Speter                                        iterpool, iterpool));
3148251881Speter      if (patch)
3149251881Speter        {
3150251881Speter          patch_target_t *target;
3151299742Sdim          svn_boolean_t filtered = FALSE;
3152251881Speter
3153251881Speter          SVN_ERR(apply_one_patch(&target, patch, abs_wc_path,
3154251881Speter                                  ctx->wc_ctx, strip_count,
3155251881Speter                                  ignore_whitespace, remove_tempfiles,
3156251881Speter                                  ctx->cancel_func, ctx->cancel_baton,
3157251881Speter                                  iterpool, iterpool));
3158299742Sdim
3159299742Sdim          if (!target->skipped && patch_func)
3160251881Speter            {
3161299742Sdim              SVN_ERR(patch_func(patch_baton, &filtered,
3162299742Sdim                                 target->canon_path_from_patchfile,
3163299742Sdim                                 target->patched_path, target->reject_path,
3164299742Sdim                                 iterpool));
3165299742Sdim            }
3166299742Sdim
3167299742Sdim          if (! filtered)
3168299742Sdim            {
3169251881Speter              /* Save info we'll still need when we're done patching. */
3170251881Speter              patch_target_info_t *target_info =
3171251881Speter                apr_pcalloc(scratch_pool, sizeof(patch_target_info_t));
3172251881Speter              target_info->local_abspath = apr_pstrdup(scratch_pool,
3173251881Speter                                                       target->local_abspath);
3174251881Speter              target_info->deleted = target->deleted;
3175251881Speter
3176251881Speter              if (! target->skipped)
3177251881Speter                {
3178251881Speter                  APR_ARRAY_PUSH(targets_info,
3179251881Speter                                 patch_target_info_t *) = target_info;
3180251881Speter
3181251881Speter                  if (target->has_text_changes
3182251881Speter                      || target->added
3183299742Sdim                      || target->move_target_abspath
3184251881Speter                      || target->deleted)
3185251881Speter                    SVN_ERR(install_patched_target(target, abs_wc_path,
3186251881Speter                                                   ctx, dry_run, iterpool));
3187251881Speter
3188251881Speter                  if (target->has_prop_changes && (!target->deleted))
3189251881Speter                    SVN_ERR(install_patched_prop_targets(target, ctx,
3190251881Speter                                                         dry_run, iterpool));
3191251881Speter
3192251881Speter                  SVN_ERR(write_out_rejected_hunks(target, dry_run, iterpool));
3193251881Speter                }
3194251881Speter              SVN_ERR(send_patch_notification(target, ctx, iterpool));
3195251881Speter
3196251881Speter              if (target->deleted && !target->skipped)
3197251881Speter                {
3198251881Speter                  SVN_ERR(check_ancestor_delete(target_info->local_abspath,
3199251881Speter                                                targets_info, abs_wc_path,
3200251881Speter                                                dry_run, ctx,
3201251881Speter                                                scratch_pool, iterpool));
3202251881Speter                }
3203251881Speter            }
3204251881Speter        }
3205251881Speter    }
3206251881Speter  while (patch);
3207251881Speter
3208251881Speter  SVN_ERR(svn_diff_close_patch_file(patch_file, iterpool));
3209251881Speter  svn_pool_destroy(iterpool);
3210251881Speter
3211251881Speter  return SVN_NO_ERROR;
3212251881Speter}
3213251881Speter
3214251881Spetersvn_error_t *
3215251881Spetersvn_client_patch(const char *patch_abspath,
3216251881Speter                 const char *wc_dir_abspath,
3217251881Speter                 svn_boolean_t dry_run,
3218251881Speter                 int strip_count,
3219251881Speter                 svn_boolean_t reverse,
3220251881Speter                 svn_boolean_t ignore_whitespace,
3221251881Speter                 svn_boolean_t remove_tempfiles,
3222251881Speter                 svn_client_patch_func_t patch_func,
3223251881Speter                 void *patch_baton,
3224251881Speter                 svn_client_ctx_t *ctx,
3225251881Speter                 apr_pool_t *scratch_pool)
3226251881Speter{
3227251881Speter  svn_node_kind_t kind;
3228251881Speter
3229251881Speter  if (strip_count < 0)
3230251881Speter    return svn_error_create(SVN_ERR_INCORRECT_PARAMS, NULL,
3231251881Speter                            _("strip count must be positive"));
3232251881Speter
3233251881Speter  if (svn_path_is_url(wc_dir_abspath))
3234251881Speter    return svn_error_createf(SVN_ERR_ILLEGAL_TARGET, NULL,
3235251881Speter                             _("'%s' is not a local path"),
3236251881Speter                             svn_dirent_local_style(wc_dir_abspath,
3237251881Speter                                                    scratch_pool));
3238251881Speter
3239251881Speter  SVN_ERR(svn_io_check_path(patch_abspath, &kind, scratch_pool));
3240251881Speter  if (kind == svn_node_none)
3241251881Speter    return svn_error_createf(SVN_ERR_ILLEGAL_TARGET, NULL,
3242251881Speter                             _("'%s' does not exist"),
3243251881Speter                             svn_dirent_local_style(patch_abspath,
3244251881Speter                                                    scratch_pool));
3245251881Speter  if (kind != svn_node_file)
3246251881Speter    return svn_error_createf(SVN_ERR_ILLEGAL_TARGET, NULL,
3247251881Speter                             _("'%s' is not a file"),
3248251881Speter                             svn_dirent_local_style(patch_abspath,
3249251881Speter                                                    scratch_pool));
3250251881Speter
3251251881Speter  SVN_ERR(svn_io_check_path(wc_dir_abspath, &kind, scratch_pool));
3252251881Speter  if (kind == svn_node_none)
3253251881Speter    return svn_error_createf(SVN_ERR_ILLEGAL_TARGET, NULL,
3254251881Speter                             _("'%s' does not exist"),
3255251881Speter                             svn_dirent_local_style(wc_dir_abspath,
3256251881Speter                                                    scratch_pool));
3257251881Speter  if (kind != svn_node_dir)
3258251881Speter    return svn_error_createf(SVN_ERR_ILLEGAL_TARGET, NULL,
3259251881Speter                             _("'%s' is not a directory"),
3260251881Speter                             svn_dirent_local_style(wc_dir_abspath,
3261251881Speter                                                    scratch_pool));
3262251881Speter
3263251881Speter  SVN_WC__CALL_WITH_WRITE_LOCK(
3264251881Speter    apply_patches(patch_abspath, wc_dir_abspath, dry_run, strip_count,
3265251881Speter                  reverse, ignore_whitespace, remove_tempfiles,
3266251881Speter                  patch_func, patch_baton, ctx, scratch_pool),
3267251881Speter    ctx->wc_ctx, wc_dir_abspath, FALSE /* lock_anchor */, scratch_pool);
3268251881Speter  return SVN_NO_ERROR;
3269251881Speter}
3270