summaryrefslogtreecommitdiffstats
path: root/subversion/libsvn_client/patch.c
diff options
context:
space:
mode:
Diffstat (limited to 'subversion/libsvn_client/patch.c')
-rw-r--r--subversion/libsvn_client/patch.c3043
1 files changed, 3043 insertions, 0 deletions
diff --git a/subversion/libsvn_client/patch.c b/subversion/libsvn_client/patch.c
new file mode 100644
index 0000000..b965646
--- /dev/null
+++ b/subversion/libsvn_client/patch.c
@@ -0,0 +1,3043 @@
+/*
+ * patch.c: patch application support
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+/* ==================================================================== */
+
+
+
+/*** Includes. ***/
+
+#include <apr_hash.h>
+#include <apr_fnmatch.h>
+#include "svn_client.h"
+#include "svn_dirent_uri.h"
+#include "svn_diff.h"
+#include "svn_hash.h"
+#include "svn_io.h"
+#include "svn_path.h"
+#include "svn_pools.h"
+#include "svn_props.h"
+#include "svn_sorts.h"
+#include "svn_subst.h"
+#include "svn_wc.h"
+#include "client.h"
+
+#include "svn_private_config.h"
+#include "private/svn_eol_private.h"
+#include "private/svn_wc_private.h"
+#include "private/svn_dep_compat.h"
+#include "private/svn_string_private.h"
+#include "private/svn_subr_private.h"
+
+typedef struct hunk_info_t {
+ /* The hunk. */
+ svn_diff_hunk_t *hunk;
+
+ /* The line where the hunk matched in the target file. */
+ svn_linenum_t matched_line;
+
+ /* Whether this hunk has been rejected. */
+ svn_boolean_t rejected;
+
+ /* Whether this hunk has already been applied (either manually
+ * or by an earlier run of patch). */
+ svn_boolean_t already_applied;
+
+ /* The fuzz factor used when matching this hunk, i.e. how many
+ * lines of leading and trailing context to ignore during matching. */
+ svn_linenum_t fuzz;
+} hunk_info_t;
+
+/* A struct carrying information related to the patched and unpatched
+ * content of a target, be it a property or the text of a file. */
+typedef struct target_content_t {
+ /* Indicates whether unpatched content existed prior to patching. */
+ svn_boolean_t existed;
+
+ /* The line last read from the unpatched content. */
+ svn_linenum_t current_line;
+
+ /* The EOL-style of the unpatched content. Either 'none', 'fixed',
+ * or 'native'. See the documentation of svn_subst_eol_style_t. */
+ svn_subst_eol_style_t eol_style;
+
+ /* If the EOL_STYLE above is not 'none', this is the EOL string
+ * corresponding to the EOL-style. Else, it is the EOL string the
+ * last line read from the target file was using. */
+ const char *eol_str;
+
+ /* An array containing apr_off_t offsets marking the beginning of
+ * each line in the unpatched content. */
+ apr_array_header_t *lines;
+
+ /* An array containing hunk_info_t structures for hunks already matched. */
+ apr_array_header_t *hunks;
+
+ /* True if end-of-file was reached while reading from the unpatched
+ * content. */
+ svn_boolean_t eof;
+
+ /* The keywords of the target. They will be contracted when reading
+ * unpatched content and expanded when writing patched content.
+ * When patching properties this hash is always empty. */
+ apr_hash_t *keywords;
+
+ /* A callback, with an associated baton, to read a line of unpatched
+ * content. */
+ svn_error_t *(*readline)(void *baton, svn_stringbuf_t **line,
+ const char **eol_str, svn_boolean_t *eof,
+ apr_pool_t *result_pool, apr_pool_t *scratch_pool);
+ void *read_baton;
+
+ /* A callback to get the current byte offset within the unpatched
+ * content. Uses the read baton. */
+ svn_error_t * (*tell)(void *baton, apr_off_t *offset,
+ apr_pool_t *scratch_pool);
+
+ /* A callback to seek to an offset within the unpatched content.
+ * Uses the read baton. */
+ svn_error_t * (*seek)(void *baton, apr_off_t offset,
+ apr_pool_t *scratch_pool);
+
+ /* A callback to write data to the patched content, with an
+ * associated baton. */
+ svn_error_t * (*write)(void *baton, const char *buf, apr_size_t len,
+ apr_pool_t *scratch_pool);
+ void *write_baton;
+
+} target_content_t;
+
+typedef struct prop_patch_target_t {
+
+ /* The name of the property */
+ const char *name;
+
+ /* The property value. This is NULL in case the property did not exist
+ * prior to patch application (see also CONTENT->existed).
+ * Note that the patch implementation does not support binary properties,
+ * so this string is not expected to contain embedded NUL characters. */
+ const svn_string_t *value;
+
+ /* The patched property value.
+ * This is equivalent to the target, except that in appropriate
+ * places it contains the modified text as it appears in the patch file. */
+ svn_stringbuf_t *patched_value;
+
+ /* All information that is specific to the content of the property. */
+ target_content_t *content;
+
+ /* Represents the operation performed on the property. It can be added,
+ * deleted or modified.
+ * ### Should we use flags instead since we're not using all enum values? */
+ svn_diff_operation_kind_t operation;
+
+ /* ### Here we'll add flags telling if the prop was added, deleted,
+ * ### had_rejects, had_local_mods prior to patching and so on. */
+} prop_patch_target_t;
+
+typedef struct patch_target_t {
+ /* The target path as it appeared in the patch file,
+ * but in canonicalised form. */
+ const char *canon_path_from_patchfile;
+
+ /* The target path, relative to the working copy directory the
+ * patch is being applied to. A patch strip count applies to this
+ * and only this path. This is never NULL. */
+ const char *local_relpath;
+
+ /* The absolute path of the target on the filesystem.
+ * Any symlinks the path from the patch file may contain are resolved.
+ * Is not always known, so it may be NULL. */
+ const char *local_abspath;
+
+ /* The target file, read-only. This is NULL in case the target
+ * file did not exist prior to patch application (see also
+ * CONTENT->existed). */
+ apr_file_t *file;
+
+ /* The target file is a symlink */
+ svn_boolean_t is_symlink;
+
+ /* The patched file.
+ * This is equivalent to the target, except that in appropriate
+ * places it contains the modified text as it appears in the patch file.
+ * The data in this file is written in repository-normal form.
+ * EOL transformation and keyword contraction is performed when the
+ * patched result is installed in the working copy. */
+ apr_file_t *patched_file;
+
+ /* Path to the patched file. */
+ const char *patched_path;
+
+ /* Hunks that are rejected will be written to this file. */
+ apr_file_t *reject_file;
+
+ /* Path to the reject file. */
+ const char *reject_path;
+
+ /* The node kind of the target as found in WC-DB prior
+ * to patch application. */
+ svn_node_kind_t db_kind;
+
+ /* The target's kind on disk prior to patch application. */
+ svn_node_kind_t kind_on_disk;
+
+ /* True if the target was locally deleted prior to patching. */
+ svn_boolean_t locally_deleted;
+
+ /* True if the target had to be skipped for some reason. */
+ svn_boolean_t skipped;
+
+ /* True if the target has been filtered by the patch callback. */
+ svn_boolean_t filtered;
+
+ /* True if at least one hunk was rejected. */
+ svn_boolean_t had_rejects;
+
+ /* True if at least one property hunk was rejected. */
+ svn_boolean_t had_prop_rejects;
+
+ /* True if the target file had local modifications before the
+ * patch was applied to it. */
+ svn_boolean_t local_mods;
+
+ /* True if the target was added by the patch, which means that it did
+ * not exist on disk before patching and has content after patching. */
+ svn_boolean_t added;
+
+ /* True if the target ended up being deleted by the patch. */
+ svn_boolean_t deleted;
+
+ /* True if the target ended up being replaced by the patch
+ * (i.e. a new file was added on top locally deleted node). */
+ svn_boolean_t replaced;
+
+ /* True if the target has the executable bit set. */
+ svn_boolean_t executable;
+
+ /* True if the patch changed the text of the target. */
+ svn_boolean_t has_text_changes;
+
+ /* True if the patch changed any of the properties of the target. */
+ svn_boolean_t has_prop_changes;
+
+ /* True if the patch contained a svn:special property. */
+ svn_boolean_t is_special;
+
+ /* All the information that is specific to the content of the target. */
+ target_content_t *content;
+
+ /* A hash table of prop_patch_target_t objects keyed by property names. */
+ apr_hash_t *prop_targets;
+
+} patch_target_t;
+
+
+/* A smaller struct containing a subset of patch_target_t.
+ * Carries the minimal amount of information we still need for a
+ * target after we're done patching it so we can free other resources. */
+typedef struct patch_target_info_t {
+ const char *local_abspath;
+ svn_boolean_t deleted;
+} patch_target_info_t;
+
+
+/* Strip STRIP_COUNT components from the front of PATH, returning
+ * the result in *RESULT, allocated in RESULT_POOL.
+ * Do temporary allocations in SCRATCH_POOL. */
+static svn_error_t *
+strip_path(const char **result, const char *path, int strip_count,
+ apr_pool_t *result_pool, apr_pool_t *scratch_pool)
+{
+ int i;
+ apr_array_header_t *components;
+ apr_array_header_t *stripped;
+
+ components = svn_path_decompose(path, scratch_pool);
+ if (strip_count > components->nelts)
+ return svn_error_createf(SVN_ERR_CLIENT_PATCH_BAD_STRIP_COUNT, NULL,
+ _("Cannot strip %u components from '%s'"),
+ strip_count,
+ svn_dirent_local_style(path, scratch_pool));
+
+ stripped = apr_array_make(scratch_pool, components->nelts - strip_count,
+ sizeof(const char *));
+ for (i = strip_count; i < components->nelts; i++)
+ {
+ const char *component;
+
+ component = APR_ARRAY_IDX(components, i, const char *);
+ APR_ARRAY_PUSH(stripped, const char *) = component;
+ }
+
+ *result = svn_path_compose(stripped, result_pool);
+
+ return SVN_NO_ERROR;
+}
+
+/* Obtain KEYWORDS, EOL_STYLE and EOL_STR for LOCAL_ABSPATH.
+ * WC_CTX is a context for the working copy the patch is applied to.
+ * Use RESULT_POOL for allocations of fields in TARGET.
+ * Use SCRATCH_POOL for all other allocations. */
+static svn_error_t *
+obtain_eol_and_keywords_for_file(apr_hash_t **keywords,
+ svn_subst_eol_style_t *eol_style,
+ const char **eol_str,
+ svn_wc_context_t *wc_ctx,
+ const char *local_abspath,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ apr_hash_t *props;
+ svn_string_t *keywords_val, *eol_style_val;
+
+ SVN_ERR(svn_wc_prop_list2(&props, wc_ctx, local_abspath,
+ scratch_pool, scratch_pool));
+ keywords_val = svn_hash_gets(props, SVN_PROP_KEYWORDS);
+ if (keywords_val)
+ {
+ svn_revnum_t changed_rev;
+ apr_time_t changed_date;
+ const char *rev_str;
+ const char *author;
+ const char *url;
+ const char *root_url;
+
+ SVN_ERR(svn_wc__node_get_changed_info(&changed_rev,
+ &changed_date,
+ &author, wc_ctx,
+ local_abspath,
+ scratch_pool,
+ scratch_pool));
+ rev_str = apr_psprintf(scratch_pool, "%ld", changed_rev);
+ SVN_ERR(svn_wc__node_get_url(&url, wc_ctx,
+ local_abspath,
+ scratch_pool, scratch_pool));
+ SVN_ERR(svn_wc__node_get_repos_info(NULL, NULL, &root_url, NULL,
+ wc_ctx, local_abspath,
+ scratch_pool, scratch_pool));
+ SVN_ERR(svn_subst_build_keywords3(keywords,
+ keywords_val->data,
+ rev_str, url, root_url, changed_date,
+ author, result_pool));
+ }
+
+ eol_style_val = svn_hash_gets(props, SVN_PROP_EOL_STYLE);
+ if (eol_style_val)
+ {
+ svn_subst_eol_style_from_value(eol_style,
+ eol_str,
+ eol_style_val->data);
+ }
+
+ return SVN_NO_ERROR;
+}
+
+/* Resolve the exact path for a patch TARGET at path PATH_FROM_PATCHFILE,
+ * which is the path of the target as it appeared in the patch file.
+ * Put a canonicalized version of PATH_FROM_PATCHFILE into
+ * TARGET->CANON_PATH_FROM_PATCHFILE.
+ * WC_CTX is a context for the working copy the patch is applied to.
+ * If possible, determine TARGET->WC_PATH, TARGET->ABS_PATH, TARGET->KIND,
+ * TARGET->ADDED, and TARGET->PARENT_DIR_EXISTS.
+ * Indicate in TARGET->SKIPPED whether the target should be skipped.
+ * STRIP_COUNT specifies the number of leading path components
+ * which should be stripped from target paths in the patch.
+ * PROP_CHANGES_ONLY specifies whether the target path is allowed to have
+ * only property changes, and no content changes (in which case the target
+ * must be a directory).
+ * Use RESULT_POOL for allocations of fields in TARGET.
+ * Use SCRATCH_POOL for all other allocations. */
+static svn_error_t *
+resolve_target_path(patch_target_t *target,
+ const char *path_from_patchfile,
+ const char *wcroot_abspath,
+ int strip_count,
+ svn_boolean_t prop_changes_only,
+ svn_wc_context_t *wc_ctx,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ const char *stripped_path;
+ svn_wc_status3_t *status;
+ svn_error_t *err;
+ svn_boolean_t under_root;
+
+ target->canon_path_from_patchfile = svn_dirent_internal_style(
+ path_from_patchfile, result_pool);
+
+ /* We allow properties to be set on the wc root dir. */
+ if (! prop_changes_only && target->canon_path_from_patchfile[0] == '\0')
+ {
+ /* An empty patch target path? What gives? Skip this. */
+ target->skipped = TRUE;
+ target->local_abspath = NULL;
+ target->local_relpath = "";
+ return SVN_NO_ERROR;
+ }
+
+ if (strip_count > 0)
+ SVN_ERR(strip_path(&stripped_path, target->canon_path_from_patchfile,
+ strip_count, result_pool, scratch_pool));
+ else
+ stripped_path = target->canon_path_from_patchfile;
+
+ if (svn_dirent_is_absolute(stripped_path))
+ {
+ target->local_relpath = svn_dirent_is_child(wcroot_abspath,
+ stripped_path,
+ result_pool);
+
+ if (! target->local_relpath)
+ {
+ /* The target path is either outside of the working copy
+ * or it is the working copy itself. Skip it. */
+ target->skipped = TRUE;
+ target->local_abspath = NULL;
+ target->local_relpath = stripped_path;
+ return SVN_NO_ERROR;
+ }
+ }
+ else
+ {
+ target->local_relpath = stripped_path;
+ }
+
+ /* Make sure the path is secure to use. We want the target to be inside
+ * of the working copy and not be fooled by symlinks it might contain. */
+ SVN_ERR(svn_dirent_is_under_root(&under_root,
+ &target->local_abspath, wcroot_abspath,
+ target->local_relpath, result_pool));
+
+ if (! under_root)
+ {
+ /* The target path is outside of the working copy. Skip it. */
+ target->skipped = TRUE;
+ target->local_abspath = NULL;
+ return SVN_NO_ERROR;
+ }
+
+ /* Skip things we should not be messing with. */
+ err = svn_wc_status3(&status, wc_ctx, target->local_abspath,
+ result_pool, scratch_pool);
+ if (err)
+ {
+ if (err->apr_err != SVN_ERR_WC_PATH_NOT_FOUND)
+ return svn_error_trace(err);
+
+ svn_error_clear(err);
+
+ target->locally_deleted = TRUE;
+ target->db_kind = svn_node_none;
+ status = NULL;
+ }
+ else if (status->node_status == svn_wc_status_ignored ||
+ status->node_status == svn_wc_status_unversioned ||
+ status->node_status == svn_wc_status_missing ||
+ status->node_status == svn_wc_status_obstructed ||
+ status->conflicted)
+ {
+ target->skipped = TRUE;
+ return SVN_NO_ERROR;
+ }
+ else if (status->node_status == svn_wc_status_deleted)
+ {
+ target->locally_deleted = TRUE;
+ }
+
+ if (status && (status->kind != svn_node_unknown))
+ target->db_kind = status->kind;
+ else
+ target->db_kind = svn_node_none;
+
+ SVN_ERR(svn_io_check_special_path(target->local_abspath,
+ &target->kind_on_disk, &target->is_symlink,
+ scratch_pool));
+
+ if (target->locally_deleted)
+ {
+ const char *moved_to_abspath;
+
+ SVN_ERR(svn_wc__node_was_moved_away(&moved_to_abspath, NULL,
+ wc_ctx, target->local_abspath,
+ result_pool, scratch_pool));
+ if (moved_to_abspath)
+ {
+ target->local_abspath = moved_to_abspath;
+ target->local_relpath = svn_dirent_skip_ancestor(wcroot_abspath,
+ moved_to_abspath);
+ SVN_ERR_ASSERT(target->local_relpath &&
+ target->local_relpath[0] != '\0');
+
+ /* As far as we are concerned this target is not locally deleted. */
+ target->locally_deleted = FALSE;
+
+ SVN_ERR(svn_io_check_special_path(target->local_abspath,
+ &target->kind_on_disk,
+ &target->is_symlink,
+ scratch_pool));
+ }
+ else if (target->kind_on_disk != svn_node_none)
+ {
+ target->skipped = TRUE;
+ return SVN_NO_ERROR;
+ }
+ }
+
+ return SVN_NO_ERROR;
+}
+
+/* Baton for reading from properties. */
+typedef struct prop_read_baton_t {
+ const svn_string_t *value;
+ apr_off_t offset;
+} prop_read_baton_t;
+
+/* Allocate *STRINGBUF in RESULT_POOL, and read into it one line from
+ * the unpatched property value accessed via BATON.
+ * Reading stops either after a line-terminator was found, or if
+ * the property value runs out in which case *EOF is set to TRUE.
+ * The line-terminator is not stored in *STRINGBUF.
+ *
+ * If the line is empty or could not be read, *line is set to NULL.
+ *
+ * The line-terminator is detected automatically and stored in *EOL
+ * if EOL is not NULL. If the end of the property value is reached
+ * and does not end with a newline character, and EOL is not NULL,
+ * *EOL is set to NULL.
+ *
+ * SCRATCH_POOL is used for temporary allocations.
+ */
+static svn_error_t *
+readline_prop(void *baton, svn_stringbuf_t **line, const char **eol_str,
+ svn_boolean_t *eof, apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ prop_read_baton_t *b = (prop_read_baton_t *)baton;
+ svn_stringbuf_t *str = NULL;
+ const char *c;
+ svn_boolean_t found_eof;
+
+ if ((apr_uint64_t)b->offset >= (apr_uint64_t)b->value->len)
+ {
+ *eol_str = NULL;
+ *eof = TRUE;
+ *line = NULL;
+ return SVN_NO_ERROR;
+ }
+
+ /* Read bytes into STR up to and including, but not storing,
+ * the next EOL sequence. */
+ *eol_str = NULL;
+ found_eof = FALSE;
+ do
+ {
+ c = b->value->data + b->offset;
+ b->offset++;
+
+ if (*c == '\0')
+ {
+ found_eof = TRUE;
+ break;
+ }
+ else if (*c == '\n')
+ {
+ *eol_str = "\n";
+ }
+ else if (*c == '\r')
+ {
+ *eol_str = "\r";
+ if (*(c + 1) == '\n')
+ {
+ *eol_str = "\r\n";
+ b->offset++;
+ }
+ }
+ else
+ {
+ if (str == NULL)
+ str = svn_stringbuf_create_ensure(80, result_pool);
+ svn_stringbuf_appendbyte(str, *c);
+ }
+
+ if (*eol_str)
+ break;
+ }
+ while (c < b->value->data + b->value->len);
+
+ if (eof)
+ *eof = found_eof;
+ *line = str;
+
+ return SVN_NO_ERROR;
+}
+
+/* Return in *OFFSET the current byte offset for reading from the
+ * unpatched property value accessed via BATON.
+ * Use SCRATCH_POOL for temporary allocations. */
+static svn_error_t *
+tell_prop(void *baton, apr_off_t *offset, apr_pool_t *scratch_pool)
+{
+ prop_read_baton_t *b = (prop_read_baton_t *)baton;
+ *offset = b->offset;
+ return SVN_NO_ERROR;
+}
+
+/* Seek to the specified by OFFSET in the unpatched property value accessed
+ * via BATON. Use SCRATCH_POOL for temporary allocations. */
+static svn_error_t *
+seek_prop(void *baton, apr_off_t offset, apr_pool_t *scratch_pool)
+{
+ prop_read_baton_t *b = (prop_read_baton_t *)baton;
+ b->offset = offset;
+ return SVN_NO_ERROR;
+}
+
+/* Write LEN bytes from BUF into the patched property value accessed
+ * via BATON. Use SCRATCH_POOL for temporary allocations. */
+static svn_error_t *
+write_prop(void *baton, const char *buf, apr_size_t len,
+ apr_pool_t *scratch_pool)
+{
+ svn_stringbuf_t *patched_value = (svn_stringbuf_t *)baton;
+ svn_stringbuf_appendbytes(patched_value, buf, len);
+ return SVN_NO_ERROR;
+}
+
+/* Initialize a PROP_TARGET structure for PROP_NAME on the patch target
+ * at LOCAL_ABSPATH. OPERATION indicates the operation performed on the
+ * property. Use working copy context WC_CTX.
+ * Allocate results in RESULT_POOL.
+ * Use SCRATCH_POOL for temporary allocations. */
+static svn_error_t *
+init_prop_target(prop_patch_target_t **prop_target,
+ const char *prop_name,
+ svn_diff_operation_kind_t operation,
+ svn_wc_context_t *wc_ctx,
+ const char *local_abspath,
+ apr_pool_t *result_pool, apr_pool_t *scratch_pool)
+{
+ prop_patch_target_t *new_prop_target;
+ target_content_t *content;
+ const svn_string_t *value;
+ svn_error_t *err;
+ prop_read_baton_t *prop_read_baton;
+
+ content = apr_pcalloc(result_pool, sizeof(*content));
+
+ /* All other fields are FALSE or NULL due to apr_pcalloc(). */
+ content->current_line = 1;
+ content->eol_style = svn_subst_eol_style_none;
+ content->lines = apr_array_make(result_pool, 0, sizeof(apr_off_t));
+ content->hunks = apr_array_make(result_pool, 0, sizeof(hunk_info_t *));
+ content->keywords = apr_hash_make(result_pool);
+
+ new_prop_target = apr_pcalloc(result_pool, sizeof(*new_prop_target));
+ new_prop_target->name = apr_pstrdup(result_pool, prop_name);
+ new_prop_target->operation = operation;
+ new_prop_target->content = content;
+
+ err = svn_wc_prop_get2(&value, wc_ctx, local_abspath, prop_name,
+ result_pool, scratch_pool);
+ if (err)
+ {
+ if (err->apr_err == SVN_ERR_WC_PATH_NOT_FOUND)
+ {
+ svn_error_clear(err);
+ value = NULL;
+ }
+ else
+ return svn_error_trace(err);
+ }
+ content->existed = (value != NULL);
+ new_prop_target->value = value;
+ new_prop_target->patched_value = svn_stringbuf_create_empty(result_pool);
+
+
+ /* Wire up the read and write callbacks. */
+ prop_read_baton = apr_pcalloc(result_pool, sizeof(*prop_read_baton));
+ prop_read_baton->value = value;
+ prop_read_baton->offset = 0;
+ content->readline = readline_prop;
+ content->tell = tell_prop;
+ content->seek = seek_prop;
+ content->read_baton = prop_read_baton;
+ content->write = write_prop;
+ content->write_baton = new_prop_target->patched_value;
+
+ *prop_target = new_prop_target;
+
+ return SVN_NO_ERROR;
+}
+
+/* Allocate *STRINGBUF in RESULT_POOL, and read into it one line from
+ * the unpatched file content accessed via BATON.
+ * Reading stops either after a line-terminator was found,
+ * or if EOF is reached in which case *EOF is set to TRUE.
+ * The line-terminator is not stored in *STRINGBUF.
+ *
+ * If the line is empty or could not be read, *line is set to NULL.
+ *
+ * The line-terminator is detected automatically and stored in *EOL
+ * if EOL is not NULL. If EOF is reached and FILE does not end
+ * with a newline character, and EOL is not NULL, *EOL is set to NULL.
+ *
+ * SCRATCH_POOL is used for temporary allocations.
+ */
+static svn_error_t *
+readline_file(void *baton, svn_stringbuf_t **line, const char **eol_str,
+ svn_boolean_t *eof, apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ apr_file_t *file = (apr_file_t *)baton;
+ svn_stringbuf_t *str = NULL;
+ apr_size_t numbytes;
+ char c;
+ svn_boolean_t found_eof;
+
+ /* Read bytes into STR up to and including, but not storing,
+ * the next EOL sequence. */
+ *eol_str = NULL;
+ numbytes = 1;
+ found_eof = FALSE;
+ while (!found_eof)
+ {
+ SVN_ERR(svn_io_file_read_full2(file, &c, sizeof(c), &numbytes,
+ &found_eof, scratch_pool));
+ if (numbytes != 1)
+ {
+ found_eof = TRUE;
+ break;
+ }
+
+ if (c == '\n')
+ {
+ *eol_str = "\n";
+ }
+ else if (c == '\r')
+ {
+ *eol_str = "\r";
+
+ if (!found_eof)
+ {
+ apr_off_t pos;
+
+ /* Check for "\r\n" by peeking at the next byte. */
+ pos = 0;
+ SVN_ERR(svn_io_file_seek(file, APR_CUR, &pos, scratch_pool));
+ SVN_ERR(svn_io_file_read_full2(file, &c, sizeof(c), &numbytes,
+ &found_eof, scratch_pool));
+ if (numbytes == 1 && c == '\n')
+ {
+ *eol_str = "\r\n";
+ }
+ else
+ {
+ /* Pretend we never peeked. */
+ SVN_ERR(svn_io_file_seek(file, APR_SET, &pos, scratch_pool));
+ found_eof = FALSE;
+ numbytes = 1;
+ }
+ }
+ }
+ else
+ {
+ if (str == NULL)
+ str = svn_stringbuf_create_ensure(80, result_pool);
+ svn_stringbuf_appendbyte(str, c);
+ }
+
+ if (*eol_str)
+ break;
+ }
+
+ if (eof)
+ *eof = found_eof;
+ *line = str;
+
+ return SVN_NO_ERROR;
+}
+
+/* Return in *OFFSET the current byte offset for reading from the
+ * unpatched file content accessed via BATON.
+ * Use SCRATCH_POOL for temporary allocations. */
+static svn_error_t *
+tell_file(void *baton, apr_off_t *offset, apr_pool_t *scratch_pool)
+{
+ apr_file_t *file = (apr_file_t *)baton;
+ *offset = 0;
+ SVN_ERR(svn_io_file_seek(file, APR_CUR, offset, scratch_pool));
+ return SVN_NO_ERROR;
+}
+
+/* Seek to the specified by OFFSET in the unpatched file content accessed
+ * via BATON. Use SCRATCH_POOL for temporary allocations. */
+static svn_error_t *
+seek_file(void *baton, apr_off_t offset, apr_pool_t *scratch_pool)
+{
+ apr_file_t *file = (apr_file_t *)baton;
+ SVN_ERR(svn_io_file_seek(file, APR_SET, &offset, scratch_pool));
+ return SVN_NO_ERROR;
+}
+
+/* Write LEN bytes from BUF into the patched file content accessed
+ * via BATON. Use SCRATCH_POOL for temporary allocations. */
+static svn_error_t *
+write_file(void *baton, const char *buf, apr_size_t len,
+ apr_pool_t *scratch_pool)
+{
+ apr_file_t *file = (apr_file_t *)baton;
+ SVN_ERR(svn_io_file_write_full(file, buf, len, &len, scratch_pool));
+ return SVN_NO_ERROR;
+}
+
+/* Handling symbolic links:
+ *
+ * In Subversion, symlinks can be represented on disk in two distinct ways.
+ * On systems which support symlinks, a symlink is created on disk.
+ * On systems which do not support symlink, a file is created on disk
+ * which contains the "normal form" of the symlink, which looks like:
+ * link TARGET
+ * where TARGET is the file the symlink points to.
+ *
+ * When reading symlinks (i.e. the link itself, not the file the symlink
+ * is pointing to) through the svn_subst_create_specialfile() function
+ * into a buffer, the buffer always contains the "normal form" of the symlink.
+ * Due to this representation symlinks always contain a single line of text.
+ *
+ * The functions below are needed to deal with the case where a patch
+ * wants to change the TARGET that a symlink points to.
+ */
+
+/* Baton for the (readline|tell|seek|write)_symlink functions. */
+struct symlink_baton_t
+{
+ /* The path to the symlink on disk (not the path to the target of the link) */
+ const char *local_abspath;
+
+ /* Indicates whether the "normal form" of the symlink has been read. */
+ svn_boolean_t at_eof;
+};
+
+/* Allocate *STRINGBUF in RESULT_POOL, and store into it the "normal form"
+ * of the symlink accessed via BATON.
+ *
+ * Otherwise behaves like readline_file(), which see.
+ */
+static svn_error_t *
+readline_symlink(void *baton, svn_stringbuf_t **line, const char **eol_str,
+ svn_boolean_t *eof, apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ struct symlink_baton_t *sb = baton;
+
+ if (eof)
+ *eof = TRUE;
+ if (eol_str)
+ *eol_str = NULL;
+
+ if (sb->at_eof)
+ {
+ *line = NULL;
+ }
+ else
+ {
+ svn_string_t *dest;
+
+ SVN_ERR(svn_io_read_link(&dest, sb->local_abspath, scratch_pool));
+ *line = svn_stringbuf_createf(result_pool, "link %s", dest->data);
+ sb->at_eof = TRUE;
+ }
+
+ return SVN_NO_ERROR;
+}
+
+/* Set *OFFSET to 1 or 0 depending on whether the "normal form" of
+ * the symlink has already been read. */
+static svn_error_t *
+tell_symlink(void *baton, apr_off_t *offset, apr_pool_t *scratch_pool)
+{
+ struct symlink_baton_t *sb = baton;
+
+ *offset = sb->at_eof ? 1 : 0;
+ return SVN_NO_ERROR;
+}
+
+/* If offset is non-zero, mark the symlink as having been read in its
+ * "normal form". Else, mark the symlink as not having been read yet. */
+static svn_error_t *
+seek_symlink(void *baton, apr_off_t offset, apr_pool_t *scratch_pool)
+{
+ struct symlink_baton_t *sb = baton;
+
+ sb->at_eof = (offset != 0);
+ return SVN_NO_ERROR;
+}
+
+
+/* Set the target of the symlink accessed via BATON.
+ * The contents of BUF must be a valid "normal form" of a symlink. */
+static svn_error_t *
+write_symlink(void *baton, const char *buf, apr_size_t len,
+ apr_pool_t *scratch_pool)
+{
+ const char *target_abspath = baton;
+ const char *new_name;
+ const char *link = apr_pstrndup(scratch_pool, buf, len);
+
+ if (strncmp(link, "link ", 5) != 0)
+ return svn_error_create(SVN_ERR_IO_WRITE_ERROR, NULL,
+ _("Invalid link representation"));
+
+ link += 5; /* Skip "link " */
+
+ /* We assume the entire symlink is written at once, as the patch
+ format is line based */
+
+ SVN_ERR(svn_io_create_unique_link(&new_name, target_abspath, link,
+ ".tmp", scratch_pool));
+
+ SVN_ERR(svn_io_file_rename(new_name, target_abspath, scratch_pool));
+
+ return SVN_NO_ERROR;
+}
+
+
+/* Return a suitable filename for the target of PATCH.
+ * Examine the ``old'' and ``new'' file names, and choose the file name
+ * with the fewest path components, the shortest basename, and the shortest
+ * total file name length (in that order). In case of a tie, return the new
+ * filename. This heuristic is also used by Larry Wall's UNIX patch (except
+ * that it prompts for a filename in case of a tie).
+ * Additionally, for compatibility with git, if one of the filenames
+ * is "/dev/null", use the other filename. */
+static const char *
+choose_target_filename(const svn_patch_t *patch)
+{
+ apr_size_t old;
+ apr_size_t new;
+
+ if (strcmp(patch->old_filename, "/dev/null") == 0)
+ return patch->new_filename;
+ if (strcmp(patch->new_filename, "/dev/null") == 0)
+ return patch->old_filename;
+
+ old = svn_path_component_count(patch->old_filename);
+ new = svn_path_component_count(patch->new_filename);
+
+ if (old == new)
+ {
+ old = strlen(svn_dirent_basename(patch->old_filename, NULL));
+ new = strlen(svn_dirent_basename(patch->new_filename, NULL));
+
+ if (old == new)
+ {
+ old = strlen(patch->old_filename);
+ new = strlen(patch->new_filename);
+ }
+ }
+
+ return (old < new) ? patch->old_filename : patch->new_filename;
+}
+
+/* Attempt to initialize a *PATCH_TARGET structure for a target file
+ * described by PATCH. Use working copy context WC_CTX.
+ * STRIP_COUNT specifies the number of leading path components
+ * which should be stripped from target paths in the patch.
+ * The patch target structure is allocated in RESULT_POOL, but if the target
+ * should be skipped, PATCH_TARGET->SKIPPED is set and the target should be
+ * treated as not fully initialized, e.g. the caller should not not do any
+ * further operations on the target if it is marked to be skipped.
+ * If REMOVE_TEMPFILES is TRUE, set up temporary files to be removed as
+ * soon as they are no longer needed.
+ * Use SCRATCH_POOL for all other allocations. */
+static svn_error_t *
+init_patch_target(patch_target_t **patch_target,
+ const svn_patch_t *patch,
+ const char *wcroot_abspath,
+ svn_wc_context_t *wc_ctx, int strip_count,
+ svn_boolean_t remove_tempfiles,
+ apr_pool_t *result_pool, apr_pool_t *scratch_pool)
+{
+ patch_target_t *target;
+ target_content_t *content;
+ svn_boolean_t has_prop_changes = FALSE;
+ svn_boolean_t prop_changes_only = FALSE;
+
+ {
+ apr_hash_index_t *hi;
+
+ for (hi = apr_hash_first(scratch_pool, patch->prop_patches);
+ hi;
+ hi = apr_hash_next(hi))
+ {
+ svn_prop_patch_t *prop_patch = svn__apr_hash_index_val(hi);
+ if (! has_prop_changes)
+ has_prop_changes = prop_patch->hunks->nelts > 0;
+ else
+ break;
+ }
+ }
+
+ prop_changes_only = has_prop_changes && patch->hunks->nelts == 0;
+
+ content = apr_pcalloc(result_pool, sizeof(*content));
+
+ /* All other fields in content are FALSE or NULL due to apr_pcalloc().*/
+ content->current_line = 1;
+ content->eol_style = svn_subst_eol_style_none;
+ content->lines = apr_array_make(result_pool, 0, sizeof(apr_off_t));
+ content->hunks = apr_array_make(result_pool, 0, sizeof(hunk_info_t *));
+ content->keywords = apr_hash_make(result_pool);
+
+ target = apr_pcalloc(result_pool, sizeof(*target));
+
+ /* All other fields in target are FALSE or NULL due to apr_pcalloc(). */
+ target->db_kind = svn_node_none;
+ target->kind_on_disk = svn_node_none;
+ target->content = content;
+ target->prop_targets = apr_hash_make(result_pool);
+
+ SVN_ERR(resolve_target_path(target, choose_target_filename(patch),
+ wcroot_abspath, strip_count, prop_changes_only,
+ wc_ctx, result_pool, scratch_pool));
+ if (! target->skipped)
+ {
+ const char *diff_header;
+ apr_size_t len;
+
+ /* Create a temporary file to write the patched result to.
+ * Also grab various bits of information about the file. */
+ if (target->is_symlink)
+ {
+ struct symlink_baton_t *sb = apr_pcalloc(result_pool, sizeof(*sb));
+ content->existed = TRUE;
+
+ sb->local_abspath = target->local_abspath;
+
+ /* Wire up the read callbacks. */
+ content->read_baton = sb;
+
+ content->readline = readline_symlink;
+ content->seek = seek_symlink;
+ content->tell = tell_symlink;
+ }
+ else if (target->kind_on_disk == svn_node_file)
+ {
+ SVN_ERR(svn_io_file_open(&target->file, target->local_abspath,
+ APR_READ | APR_BUFFERED,
+ APR_OS_DEFAULT, result_pool));
+ SVN_ERR(svn_wc_text_modified_p2(&target->local_mods, wc_ctx,
+ target->local_abspath, FALSE,
+ scratch_pool));
+ SVN_ERR(svn_io_is_file_executable(&target->executable,
+ target->local_abspath,
+ scratch_pool));
+ SVN_ERR(obtain_eol_and_keywords_for_file(&content->keywords,
+ &content->eol_style,
+ &content->eol_str,
+ wc_ctx,
+ target->local_abspath,
+ result_pool,
+ scratch_pool));
+ content->existed = TRUE;
+
+ /* Wire up the read callbacks. */
+ content->readline = readline_file;
+ content->seek = seek_file;
+ content->tell = tell_file;
+ content->read_baton = target->file;
+ }
+
+ /* ### Is it ok to set the operation of the target already here? Isn't
+ * ### the target supposed to be marked with an operation after we have
+ * ### determined that the changes will apply cleanly to the WC? Maybe
+ * ### we should have kept the patch field in patch_target_t to be
+ * ### able to distinguish between 'what the patch says we should do'
+ * ### and 'what we can do with the given state of our WC'. */
+ if (patch->operation == svn_diff_op_added)
+ target->added = TRUE;
+ else if (patch->operation == svn_diff_op_deleted)
+ target->deleted = TRUE;
+
+ if (! target->is_symlink)
+ {
+ /* Open a temporary file to write the patched result to. */
+ SVN_ERR(svn_io_open_unique_file3(&target->patched_file,
+ &target->patched_path, NULL,
+ remove_tempfiles ?
+ svn_io_file_del_on_pool_cleanup :
+ svn_io_file_del_none,
+ result_pool, scratch_pool));
+
+ /* Put the write callback in place. */
+ content->write = write_file;
+ content->write_baton = target->patched_file;
+ }
+ else
+ {
+ /* Put the write callback in place. */
+ SVN_ERR(svn_io_open_unique_file3(NULL,
+ &target->patched_path, NULL,
+ remove_tempfiles ?
+ svn_io_file_del_on_pool_cleanup :
+ svn_io_file_del_none,
+ result_pool, scratch_pool));
+
+ content->write_baton = (void*)target->patched_path;
+
+ content->write = write_symlink;
+ }
+
+ /* Open a temporary file to write rejected hunks to. */
+ SVN_ERR(svn_io_open_unique_file3(&target->reject_file,
+ &target->reject_path, NULL,
+ remove_tempfiles ?
+ svn_io_file_del_on_pool_cleanup :
+ svn_io_file_del_none,
+ result_pool, scratch_pool));
+
+ /* The reject file needs a diff header. */
+ diff_header = apr_psprintf(scratch_pool, "--- %s%s+++ %s%s",
+ target->canon_path_from_patchfile,
+ APR_EOL_STR,
+ target->canon_path_from_patchfile,
+ APR_EOL_STR);
+ len = strlen(diff_header);
+ SVN_ERR(svn_io_file_write_full(target->reject_file, diff_header, len,
+ &len, scratch_pool));
+
+ /* Handle properties. */
+ if (! target->skipped)
+ {
+ apr_hash_index_t *hi;
+
+ for (hi = apr_hash_first(result_pool, patch->prop_patches);
+ hi;
+ hi = apr_hash_next(hi))
+ {
+ const char *prop_name = svn__apr_hash_index_key(hi);
+ svn_prop_patch_t *prop_patch = svn__apr_hash_index_val(hi);
+ prop_patch_target_t *prop_target;
+
+ SVN_ERR(init_prop_target(&prop_target,
+ prop_name,
+ prop_patch->operation,
+ wc_ctx, target->local_abspath,
+ result_pool, scratch_pool));
+ svn_hash_sets(target->prop_targets, prop_name, prop_target);
+ }
+ }
+ }
+
+ *patch_target = target;
+ return SVN_NO_ERROR;
+}
+
+/* Read a *LINE from CONTENT. If the line has not been read before
+ * mark the line in CONTENT->LINES.
+ * If a line could be read successfully, increase CONTENT->CURRENT_LINE,
+ * and allocate *LINE in RESULT_POOL.
+ * Do temporary allocations in SCRATCH_POOL.
+ */
+static svn_error_t *
+readline(target_content_t *content,
+ const char **line,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ svn_stringbuf_t *line_raw;
+ const char *eol_str;
+ svn_linenum_t max_line = (svn_linenum_t)content->lines->nelts + 1;
+
+ if (content->eof || content->readline == NULL)
+ {
+ *line = "";
+ return SVN_NO_ERROR;
+ }
+
+ SVN_ERR_ASSERT(content->current_line <= max_line);
+ if (content->current_line == max_line)
+ {
+ apr_off_t offset;
+
+ SVN_ERR(content->tell(content->read_baton, &offset,
+ scratch_pool));
+ APR_ARRAY_PUSH(content->lines, apr_off_t) = offset;
+ }
+
+ SVN_ERR(content->readline(content->read_baton, &line_raw,
+ &eol_str, &content->eof,
+ result_pool, scratch_pool));
+ if (content->eol_style == svn_subst_eol_style_none)
+ content->eol_str = eol_str;
+
+ if (line_raw)
+ {
+ /* Contract keywords. */
+ SVN_ERR(svn_subst_translate_cstring2(line_raw->data, line,
+ NULL, FALSE,
+ content->keywords, FALSE,
+ result_pool));
+ }
+ else
+ *line = "";
+
+ if ((line_raw && line_raw->len > 0) || eol_str)
+ content->current_line++;
+
+ SVN_ERR_ASSERT(content->current_line > 0);
+
+ return SVN_NO_ERROR;
+}
+
+/* Seek to the specified LINE in CONTENT.
+ * Mark any lines not read before in CONTENT->LINES.
+ * Do temporary allocations in SCRATCH_POOL.
+ */
+static svn_error_t *
+seek_to_line(target_content_t *content, svn_linenum_t line,
+ apr_pool_t *scratch_pool)
+{
+ svn_linenum_t saved_line;
+ svn_boolean_t saved_eof;
+
+ SVN_ERR_ASSERT(line > 0);
+
+ if (line == content->current_line)
+ return SVN_NO_ERROR;
+
+ saved_line = content->current_line;
+ saved_eof = content->eof;
+
+ if (line <= (svn_linenum_t)content->lines->nelts)
+ {
+ apr_off_t offset;
+
+ offset = APR_ARRAY_IDX(content->lines, line - 1, apr_off_t);
+ SVN_ERR(content->seek(content->read_baton, offset,
+ scratch_pool));
+ content->current_line = line;
+ }
+ else
+ {
+ const char *dummy;
+ apr_pool_t *iterpool = svn_pool_create(scratch_pool);
+
+ while (! content->eof && content->current_line < line)
+ {
+ svn_pool_clear(iterpool);
+ SVN_ERR(readline(content, &dummy, iterpool, iterpool));
+ }
+ svn_pool_destroy(iterpool);
+ }
+
+ /* After seeking backwards from EOF position clear EOF indicator. */
+ if (saved_eof && saved_line > content->current_line)
+ content->eof = FALSE;
+
+ return SVN_NO_ERROR;
+}
+
+/* Indicate in *MATCHED whether the original text of HUNK matches the patch
+ * CONTENT at its current line. Lines within FUZZ lines of the start or
+ * end of HUNK will always match. If IGNORE_WHITESPACE is set, we ignore
+ * whitespace when doing the matching. When this function returns, neither
+ * CONTENT->CURRENT_LINE nor the file offset in the target file will
+ * have changed. If MATCH_MODIFIED is TRUE, match the modified hunk text,
+ * rather than the original hunk text.
+ * Do temporary allocations in POOL. */
+static svn_error_t *
+match_hunk(svn_boolean_t *matched, target_content_t *content,
+ svn_diff_hunk_t *hunk, svn_linenum_t fuzz,
+ svn_boolean_t ignore_whitespace,
+ svn_boolean_t match_modified, apr_pool_t *pool)
+{
+ svn_stringbuf_t *hunk_line;
+ const char *target_line;
+ svn_linenum_t lines_read;
+ svn_linenum_t saved_line;
+ svn_boolean_t hunk_eof;
+ svn_boolean_t lines_matched;
+ apr_pool_t *iterpool;
+ svn_linenum_t hunk_length;
+ svn_linenum_t leading_context;
+ svn_linenum_t trailing_context;
+
+ *matched = FALSE;
+
+ if (content->eof)
+ return SVN_NO_ERROR;
+
+ saved_line = content->current_line;
+ lines_read = 0;
+ lines_matched = FALSE;
+ leading_context = svn_diff_hunk_get_leading_context(hunk);
+ trailing_context = svn_diff_hunk_get_trailing_context(hunk);
+ if (match_modified)
+ {
+ svn_diff_hunk_reset_modified_text(hunk);
+ hunk_length = svn_diff_hunk_get_modified_length(hunk);
+ }
+ else
+ {
+ svn_diff_hunk_reset_original_text(hunk);
+ hunk_length = svn_diff_hunk_get_original_length(hunk);
+ }
+ iterpool = svn_pool_create(pool);
+ do
+ {
+ const char *hunk_line_translated;
+
+ svn_pool_clear(iterpool);
+
+ if (match_modified)
+ SVN_ERR(svn_diff_hunk_readline_modified_text(hunk, &hunk_line,
+ NULL, &hunk_eof,
+ iterpool, iterpool));
+ else
+ SVN_ERR(svn_diff_hunk_readline_original_text(hunk, &hunk_line,
+ NULL, &hunk_eof,
+ iterpool, iterpool));
+
+ /* Contract keywords, if any, before matching. */
+ SVN_ERR(svn_subst_translate_cstring2(hunk_line->data,
+ &hunk_line_translated,
+ NULL, FALSE,
+ content->keywords, FALSE,
+ iterpool));
+ SVN_ERR(readline(content, &target_line, iterpool, iterpool));
+
+ lines_read++;
+
+ /* If the last line doesn't have a newline, we get EOF but still
+ * have a non-empty line to compare. */
+ if ((hunk_eof && hunk_line->len == 0) ||
+ (content->eof && *target_line == 0))
+ break;
+
+ /* Leading/trailing fuzzy lines always match. */
+ if ((lines_read <= fuzz && leading_context > fuzz) ||
+ (lines_read > hunk_length - fuzz && trailing_context > fuzz))
+ lines_matched = TRUE;
+ else
+ {
+ if (ignore_whitespace)
+ {
+ char *hunk_line_trimmed;
+ char *target_line_trimmed;
+
+ hunk_line_trimmed = apr_pstrdup(iterpool, hunk_line_translated);
+ target_line_trimmed = apr_pstrdup(iterpool, target_line);
+ apr_collapse_spaces(hunk_line_trimmed, hunk_line_trimmed);
+ apr_collapse_spaces(target_line_trimmed, target_line_trimmed);
+ lines_matched = ! strcmp(hunk_line_trimmed, target_line_trimmed);
+ }
+ else
+ lines_matched = ! strcmp(hunk_line_translated, target_line);
+ }
+ }
+ while (lines_matched);
+
+ *matched = lines_matched && hunk_eof && hunk_line->len == 0;
+ SVN_ERR(seek_to_line(content, saved_line, iterpool));
+ svn_pool_destroy(iterpool);
+
+ return SVN_NO_ERROR;
+}
+
+/* Scan lines of CONTENT for a match of the original text of HUNK,
+ * up to but not including the specified UPPER_LINE. Use fuzz factor FUZZ.
+ * If UPPER_LINE is zero scan until EOF occurs when reading from TARGET.
+ * Return the line at which HUNK was matched in *MATCHED_LINE.
+ * If the hunk did not match at all, set *MATCHED_LINE to zero.
+ * If the hunk matched multiple times, and MATCH_FIRST is TRUE,
+ * return the line number at which the first match occurred in *MATCHED_LINE.
+ * If the hunk matched multiple times, and MATCH_FIRST is FALSE,
+ * return the line number at which the last match occurred in *MATCHED_LINE.
+ * If IGNORE_WHITESPACE is set, ignore whitespace during the matching.
+ * If MATCH_MODIFIED is TRUE, match the modified hunk text,
+ * rather than the original hunk text.
+ * Call cancel CANCEL_FUNC with baton CANCEL_BATON to trigger cancellation.
+ * Do all allocations in POOL. */
+static svn_error_t *
+scan_for_match(svn_linenum_t *matched_line,
+ target_content_t *content,
+ svn_diff_hunk_t *hunk, svn_boolean_t match_first,
+ svn_linenum_t upper_line, svn_linenum_t fuzz,
+ svn_boolean_t ignore_whitespace,
+ svn_boolean_t match_modified,
+ svn_cancel_func_t cancel_func, void *cancel_baton,
+ apr_pool_t *pool)
+{
+ apr_pool_t *iterpool;
+
+ *matched_line = 0;
+ iterpool = svn_pool_create(pool);
+ while ((content->current_line < upper_line || upper_line == 0) &&
+ ! content->eof)
+ {
+ svn_boolean_t matched;
+
+ svn_pool_clear(iterpool);
+
+ if (cancel_func)
+ SVN_ERR(cancel_func(cancel_baton));
+
+ SVN_ERR(match_hunk(&matched, content, hunk, fuzz, ignore_whitespace,
+ match_modified, iterpool));
+ if (matched)
+ {
+ svn_boolean_t taken = FALSE;
+ int i;
+
+ /* Don't allow hunks to match at overlapping locations. */
+ for (i = 0; i < content->hunks->nelts; i++)
+ {
+ const hunk_info_t *hi;
+ svn_linenum_t length;
+
+ hi = APR_ARRAY_IDX(content->hunks, i, const hunk_info_t *);
+
+ if (match_modified)
+ length = svn_diff_hunk_get_modified_length(hi->hunk);
+ else
+ length = svn_diff_hunk_get_original_length(hi->hunk);
+
+ taken = (! hi->rejected &&
+ content->current_line >= hi->matched_line &&
+ content->current_line < (hi->matched_line + length));
+ if (taken)
+ break;
+ }
+
+ if (! taken)
+ {
+ *matched_line = content->current_line;
+ if (match_first)
+ break;
+ }
+ }
+
+ if (! content->eof)
+ SVN_ERR(seek_to_line(content, content->current_line + 1,
+ iterpool));
+ }
+ svn_pool_destroy(iterpool);
+
+ return SVN_NO_ERROR;
+}
+
+/* Indicate in *MATCH whether the content described by CONTENT
+ * matches the modified text of HUNK.
+ * Use SCRATCH_POOL for temporary allocations. */
+static svn_error_t *
+match_existing_target(svn_boolean_t *match,
+ target_content_t *content,
+ svn_diff_hunk_t *hunk,
+ apr_pool_t *scratch_pool)
+{
+ svn_boolean_t lines_matched;
+ apr_pool_t *iterpool;
+ svn_boolean_t hunk_eof;
+ svn_linenum_t saved_line;
+
+ svn_diff_hunk_reset_modified_text(hunk);
+
+ saved_line = content->current_line;
+
+ iterpool = svn_pool_create(scratch_pool);
+ do
+ {
+ const char *line;
+ svn_stringbuf_t *hunk_line;
+ const char *line_translated;
+ const char *hunk_line_translated;
+
+ svn_pool_clear(iterpool);
+
+ SVN_ERR(readline(content, &line, iterpool, iterpool));
+ SVN_ERR(svn_diff_hunk_readline_modified_text(hunk, &hunk_line,
+ NULL, &hunk_eof,
+ iterpool, iterpool));
+ /* Contract keywords. */
+ SVN_ERR(svn_subst_translate_cstring2(line, &line_translated,
+ NULL, FALSE,
+ content->keywords,
+ FALSE, iterpool));
+ SVN_ERR(svn_subst_translate_cstring2(hunk_line->data,
+ &hunk_line_translated,
+ NULL, FALSE,
+ content->keywords,
+ FALSE, iterpool));
+ lines_matched = ! strcmp(line_translated, hunk_line_translated);
+ if (content->eof != hunk_eof)
+ {
+ svn_pool_destroy(iterpool);
+ *match = FALSE;
+ return SVN_NO_ERROR;
+ }
+ }
+ while (lines_matched && ! content->eof && ! hunk_eof);
+ svn_pool_destroy(iterpool);
+
+ *match = (lines_matched && content->eof == hunk_eof);
+ SVN_ERR(seek_to_line(content, saved_line, scratch_pool));
+
+ return SVN_NO_ERROR;
+}
+
+/* Determine the line at which a HUNK applies to CONTENT of the TARGET
+ * file, and return an appropriate hunk_info object in *HI, allocated from
+ * RESULT_POOL. Use fuzz factor FUZZ. Set HI->FUZZ to FUZZ. If no correct
+ * line can be determined, set HI->REJECTED to TRUE.
+ * IGNORE_WHITESPACE tells whether whitespace should be considered when
+ * matching. IS_PROP_HUNK indicates whether the hunk patches file content
+ * or a property.
+ * When this function returns, neither CONTENT->CURRENT_LINE nor
+ * the file offset in the target file will have changed.
+ * Call cancel CANCEL_FUNC with baton CANCEL_BATON to trigger cancellation.
+ * Do temporary allocations in POOL. */
+static svn_error_t *
+get_hunk_info(hunk_info_t **hi, patch_target_t *target,
+ target_content_t *content,
+ svn_diff_hunk_t *hunk, svn_linenum_t fuzz,
+ svn_boolean_t ignore_whitespace,
+ svn_boolean_t is_prop_hunk,
+ svn_cancel_func_t cancel_func, void *cancel_baton,
+ apr_pool_t *result_pool, apr_pool_t *scratch_pool)
+{
+ svn_linenum_t matched_line;
+ svn_linenum_t original_start;
+ svn_boolean_t already_applied;
+
+ original_start = svn_diff_hunk_get_original_start(hunk);
+ already_applied = FALSE;
+
+ /* An original offset of zero means that this hunk wants to create
+ * a new file. Don't bother matching hunks in that case, since
+ * the hunk applies at line 1. If the file already exists, the hunk
+ * is rejected, unless the file is versioned and its content matches
+ * the file the patch wants to create. */
+ if (original_start == 0 && fuzz > 0)
+ {
+ matched_line = 0; /* reject any fuzz for new files */
+ }
+ else if (original_start == 0 && ! is_prop_hunk)
+ {
+ if (target->kind_on_disk == svn_node_file)
+ {
+ const svn_io_dirent2_t *dirent;
+ SVN_ERR(svn_io_stat_dirent2(&dirent, target->local_abspath, FALSE,
+ TRUE, scratch_pool, scratch_pool));
+
+ if (dirent->kind == svn_node_file
+ && !dirent->special
+ && dirent->filesize == 0)
+ {
+ matched_line = 1; /* Matched an on-disk empty file */
+ }
+ else
+ {
+ if (target->db_kind == svn_node_file)
+ {
+ svn_boolean_t file_matches;
+
+ /* ### I can't reproduce anything but a no-match here.
+ The content is already at eof, so any hunk fails */
+ SVN_ERR(match_existing_target(&file_matches, content, hunk,
+ scratch_pool));
+ if (file_matches)
+ {
+ matched_line = 1;
+ already_applied = TRUE;
+ }
+ else
+ matched_line = 0; /* reject */
+ }
+ else
+ matched_line = 0; /* reject */
+ }
+ }
+ else
+ matched_line = 1;
+ }
+ /* Same conditions apply as for the file case above.
+ *
+ * ### Since the hunk says the prop should be added we just assume so for
+ * ### now and don't bother with storing the previous lines and such. When
+ * ### we have the diff operation available we can just check for adds. */
+ else if (original_start == 0 && is_prop_hunk)
+ {
+ if (content->existed)
+ {
+ svn_boolean_t prop_matches;
+
+ SVN_ERR(match_existing_target(&prop_matches, content, hunk,
+ scratch_pool));
+
+ if (prop_matches)
+ {
+ matched_line = 1;
+ already_applied = TRUE;
+ }
+ else
+ matched_line = 0; /* reject */
+ }
+ else
+ matched_line = 1;
+ }
+ else if (original_start > 0 && content->existed)
+ {
+ svn_linenum_t saved_line = content->current_line;
+
+ /* Scan for a match at the line where the hunk thinks it
+ * should be going. */
+ SVN_ERR(seek_to_line(content, original_start, scratch_pool));
+ if (content->current_line != original_start)
+ {
+ /* Seek failed. */
+ matched_line = 0;
+ }
+ else
+ SVN_ERR(scan_for_match(&matched_line, content, hunk, TRUE,
+ original_start + 1, fuzz,
+ ignore_whitespace, FALSE,
+ cancel_func, cancel_baton,
+ scratch_pool));
+
+ if (matched_line != original_start)
+ {
+ /* Check if the hunk is already applied.
+ * We only check for an exact match here, and don't bother checking
+ * for already applied patches with offset/fuzz, because such a
+ * check would be ambiguous. */
+ if (fuzz == 0)
+ {
+ svn_linenum_t modified_start;
+
+ modified_start = svn_diff_hunk_get_modified_start(hunk);
+ if (modified_start == 0)
+ {
+ /* Patch wants to delete the file. */
+ already_applied = target->locally_deleted;
+ }
+ else
+ {
+ SVN_ERR(seek_to_line(content, modified_start,
+ scratch_pool));
+ SVN_ERR(scan_for_match(&matched_line, content,
+ hunk, TRUE,
+ modified_start + 1,
+ fuzz, ignore_whitespace, TRUE,
+ cancel_func, cancel_baton,
+ scratch_pool));
+ already_applied = (matched_line == modified_start);
+ }
+ }
+ else
+ already_applied = FALSE;
+
+ if (! already_applied)
+ {
+ /* Scan the whole file again from the start. */
+ SVN_ERR(seek_to_line(content, 1, scratch_pool));
+
+ /* Scan forward towards the hunk's line and look for a line
+ * where the hunk matches. */
+ SVN_ERR(scan_for_match(&matched_line, content, hunk, FALSE,
+ original_start, fuzz,
+ ignore_whitespace, FALSE,
+ cancel_func, cancel_baton,
+ scratch_pool));
+
+ /* In tie-break situations, we arbitrarily prefer early matches
+ * to save us from scanning the rest of the file. */
+ if (matched_line == 0)
+ {
+ /* Scan forward towards the end of the file and look
+ * for a line where the hunk matches. */
+ SVN_ERR(scan_for_match(&matched_line, content, hunk,
+ TRUE, 0, fuzz, ignore_whitespace,
+ FALSE, cancel_func, cancel_baton,
+ scratch_pool));
+ }
+ }
+ }
+
+ SVN_ERR(seek_to_line(content, saved_line, scratch_pool));
+ }
+ else
+ {
+ /* The hunk wants to modify a file which doesn't exist. */
+ matched_line = 0;
+ }
+
+ (*hi) = apr_pcalloc(result_pool, sizeof(hunk_info_t));
+ (*hi)->hunk = hunk;
+ (*hi)->matched_line = matched_line;
+ (*hi)->rejected = (matched_line == 0);
+ (*hi)->already_applied = already_applied;
+ (*hi)->fuzz = fuzz;
+
+ return SVN_NO_ERROR;
+}
+
+/* Copy lines to the patched content until the specified LINE has been
+ * reached. Indicate in *EOF whether end-of-file was encountered while
+ * reading from the target.
+ * If LINE is zero, copy lines until end-of-file has been reached.
+ * Do all allocations in POOL. */
+static svn_error_t *
+copy_lines_to_target(target_content_t *content, svn_linenum_t line,
+ apr_pool_t *pool)
+{
+ apr_pool_t *iterpool;
+
+ iterpool = svn_pool_create(pool);
+ while ((content->current_line < line || line == 0) && ! content->eof)
+ {
+ const char *target_line;
+ apr_size_t len;
+
+ svn_pool_clear(iterpool);
+
+ SVN_ERR(readline(content, &target_line, iterpool, iterpool));
+ if (! content->eof)
+ target_line = apr_pstrcat(iterpool, target_line, content->eol_str,
+ (char *)NULL);
+ len = strlen(target_line);
+ SVN_ERR(content->write(content->write_baton, target_line,
+ len, iterpool));
+ }
+ svn_pool_destroy(iterpool);
+
+ return SVN_NO_ERROR;
+}
+
+/* Write the diff text of HUNK to TARGET's reject file,
+ * and mark TARGET as having had rejects.
+ * We don't expand keywords, nor normalise line-endings, in reject files.
+ * Do temporary allocations in SCRATCH_POOL. */
+static svn_error_t *
+reject_hunk(patch_target_t *target, target_content_t *content,
+ svn_diff_hunk_t *hunk, const char *prop_name,
+ apr_pool_t *pool)
+{
+ const char *hunk_header;
+ apr_size_t len;
+ svn_boolean_t eof;
+ static const char * const text_atat = "@@";
+ static const char * const prop_atat = "##";
+ const char *atat;
+ apr_pool_t *iterpool;
+
+ if (prop_name)
+ {
+ const char *prop_header;
+
+ /* ### Print 'Added', 'Deleted' or 'Modified' instead of 'Property'.
+ */
+ prop_header = apr_psprintf(pool, "Property: %s\n", prop_name);
+ len = strlen(prop_header);
+ SVN_ERR(svn_io_file_write_full(target->reject_file, prop_header,
+ len, &len, pool));
+ atat = prop_atat;
+ }
+ else
+ {
+ atat = text_atat;
+ }
+
+ hunk_header = apr_psprintf(pool, "%s -%lu,%lu +%lu,%lu %s%s",
+ atat,
+ svn_diff_hunk_get_original_start(hunk),
+ svn_diff_hunk_get_original_length(hunk),
+ svn_diff_hunk_get_modified_start(hunk),
+ svn_diff_hunk_get_modified_length(hunk),
+ atat,
+ APR_EOL_STR);
+ len = strlen(hunk_header);
+ SVN_ERR(svn_io_file_write_full(target->reject_file, hunk_header, len,
+ &len, pool));
+
+ iterpool = svn_pool_create(pool);
+ do
+ {
+ svn_stringbuf_t *hunk_line;
+ const char *eol_str;
+
+ svn_pool_clear(iterpool);
+
+ SVN_ERR(svn_diff_hunk_readline_diff_text(hunk, &hunk_line, &eol_str,
+ &eof, iterpool, iterpool));
+ if (! eof)
+ {
+ if (hunk_line->len >= 1)
+ {
+ len = hunk_line->len;
+ SVN_ERR(svn_io_file_write_full(target->reject_file,
+ hunk_line->data, len, &len,
+ iterpool));
+ }
+
+ if (eol_str)
+ {
+ len = strlen(eol_str);
+ SVN_ERR(svn_io_file_write_full(target->reject_file, eol_str,
+ len, &len, iterpool));
+ }
+ }
+ }
+ while (! eof);
+ svn_pool_destroy(iterpool);
+
+ if (prop_name)
+ target->had_prop_rejects = TRUE;
+ else
+ target->had_rejects = TRUE;
+
+ return SVN_NO_ERROR;
+}
+
+/* Write the modified text of the hunk described by HI to the patched
+ * CONTENT. TARGET is the patch target.
+ * If PROP_NAME is not NULL, the hunk is assumed to be targeted for
+ * a property with the given name.
+ * Do temporary allocations in POOL. */
+static svn_error_t *
+apply_hunk(patch_target_t *target, target_content_t *content,
+ hunk_info_t *hi, const char *prop_name, apr_pool_t *pool)
+{
+ svn_linenum_t lines_read;
+ svn_boolean_t eof;
+ apr_pool_t *iterpool;
+
+ /* ### Is there a cleaner way to describe if we have an existing target?
+ */
+ if (target->kind_on_disk == svn_node_file || prop_name)
+ {
+ svn_linenum_t line;
+
+ /* Move forward to the hunk's line, copying data as we go.
+ * Also copy leading lines of context which matched with fuzz.
+ * The target has changed on the fuzzy-matched lines,
+ * so we should retain the target's version of those lines. */
+ SVN_ERR(copy_lines_to_target(content, hi->matched_line + hi->fuzz,
+ pool));
+
+ /* Skip the target's version of the hunk.
+ * Don't skip trailing lines which matched with fuzz. */
+ line = content->current_line +
+ svn_diff_hunk_get_original_length(hi->hunk) - (2 * hi->fuzz);
+ SVN_ERR(seek_to_line(content, line, pool));
+ if (content->current_line != line && ! content->eof)
+ {
+ /* Seek failed, reject this hunk. */
+ hi->rejected = TRUE;
+ SVN_ERR(reject_hunk(target, content, hi->hunk, prop_name, pool));
+ return SVN_NO_ERROR;
+ }
+ }
+
+ /* Write the hunk's version to the patched result.
+ * Don't write the lines which matched with fuzz. */
+ lines_read = 0;
+ svn_diff_hunk_reset_modified_text(hi->hunk);
+ iterpool = svn_pool_create(pool);
+ do
+ {
+ svn_stringbuf_t *hunk_line;
+ const char *eol_str;
+
+ svn_pool_clear(iterpool);
+
+ SVN_ERR(svn_diff_hunk_readline_modified_text(hi->hunk, &hunk_line,
+ &eol_str, &eof,
+ iterpool, iterpool));
+ lines_read++;
+ if (lines_read > hi->fuzz &&
+ lines_read <= svn_diff_hunk_get_modified_length(hi->hunk) - hi->fuzz)
+ {
+ apr_size_t len;
+
+ if (hunk_line->len >= 1)
+ {
+ len = hunk_line->len;
+ SVN_ERR(content->write(content->write_baton,
+ hunk_line->data, len, iterpool));
+ }
+
+ if (eol_str)
+ {
+ /* Use the EOL as it was read from the patch file,
+ * unless the target's EOL style is set by svn:eol-style */
+ if (content->eol_style != svn_subst_eol_style_none)
+ eol_str = content->eol_str;
+
+ len = strlen(eol_str);
+ SVN_ERR(content->write(content->write_baton,
+ eol_str, len, iterpool));
+ }
+ }
+ }
+ while (! eof);
+ svn_pool_destroy(iterpool);
+
+ if (prop_name)
+ target->has_prop_changes = TRUE;
+ else
+ target->has_text_changes = TRUE;
+
+ return SVN_NO_ERROR;
+}
+
+/* Use client context CTX to send a suitable notification for hunk HI,
+ * using TARGET to determine the path. If the hunk is a property hunk,
+ * PROP_NAME must be the name of the property, else NULL.
+ * Use POOL for temporary allocations. */
+static svn_error_t *
+send_hunk_notification(const hunk_info_t *hi,
+ const patch_target_t *target,
+ const char *prop_name,
+ const svn_client_ctx_t *ctx,
+ apr_pool_t *pool)
+{
+ svn_wc_notify_t *notify;
+ svn_wc_notify_action_t action;
+
+ if (hi->already_applied)
+ action = svn_wc_notify_patch_hunk_already_applied;
+ else if (hi->rejected)
+ action = svn_wc_notify_patch_rejected_hunk;
+ else
+ action = svn_wc_notify_patch_applied_hunk;
+
+ notify = svn_wc_create_notify(target->local_abspath
+ ? target->local_abspath
+ : target->local_relpath,
+ action, pool);
+ notify->hunk_original_start =
+ svn_diff_hunk_get_original_start(hi->hunk);
+ notify->hunk_original_length =
+ svn_diff_hunk_get_original_length(hi->hunk);
+ notify->hunk_modified_start =
+ svn_diff_hunk_get_modified_start(hi->hunk);
+ notify->hunk_modified_length =
+ svn_diff_hunk_get_modified_length(hi->hunk);
+ notify->hunk_matched_line = hi->matched_line;
+ notify->hunk_fuzz = hi->fuzz;
+ notify->prop_name = prop_name;
+
+ (*ctx->notify_func2)(ctx->notify_baton2, notify, pool);
+
+ return SVN_NO_ERROR;
+}
+
+/* Use client context CTX to send a suitable notification for a patch TARGET.
+ * Use POOL for temporary allocations. */
+static svn_error_t *
+send_patch_notification(const patch_target_t *target,
+ const svn_client_ctx_t *ctx,
+ apr_pool_t *pool)
+{
+ svn_wc_notify_t *notify;
+ svn_wc_notify_action_t action;
+
+ if (! ctx->notify_func2)
+ return SVN_NO_ERROR;
+
+ if (target->skipped)
+ action = svn_wc_notify_skip;
+ else if (target->deleted)
+ action = svn_wc_notify_delete;
+ else if (target->added || target->replaced)
+ action = svn_wc_notify_add;
+ else
+ action = svn_wc_notify_patch;
+
+ notify = svn_wc_create_notify(target->local_abspath ? target->local_abspath
+ : target->local_relpath,
+ action, pool);
+ notify->kind = svn_node_file;
+
+ if (action == svn_wc_notify_skip)
+ {
+ if (target->db_kind == svn_node_none ||
+ target->db_kind == svn_node_unknown)
+ notify->content_state = svn_wc_notify_state_missing;
+ else if (target->db_kind == svn_node_dir)
+ notify->content_state = svn_wc_notify_state_obstructed;
+ else
+ notify->content_state = svn_wc_notify_state_unknown;
+ }
+ else
+ {
+ if (target->had_rejects)
+ notify->content_state = svn_wc_notify_state_conflicted;
+ else if (target->local_mods)
+ notify->content_state = svn_wc_notify_state_merged;
+ else if (target->has_text_changes)
+ notify->content_state = svn_wc_notify_state_changed;
+
+ if (target->had_prop_rejects)
+ notify->prop_state = svn_wc_notify_state_conflicted;
+ else if (target->has_prop_changes)
+ notify->prop_state = svn_wc_notify_state_changed;
+ }
+
+ (*ctx->notify_func2)(ctx->notify_baton2, notify, pool);
+
+ if (action == svn_wc_notify_patch)
+ {
+ int i;
+ apr_pool_t *iterpool;
+ apr_hash_index_t *hash_index;
+
+ iterpool = svn_pool_create(pool);
+ for (i = 0; i < target->content->hunks->nelts; i++)
+ {
+ const hunk_info_t *hi;
+
+ svn_pool_clear(iterpool);
+
+ hi = APR_ARRAY_IDX(target->content->hunks, i, hunk_info_t *);
+
+ SVN_ERR(send_hunk_notification(hi, target, NULL /* prop_name */,
+ ctx, iterpool));
+ }
+
+ for (hash_index = apr_hash_first(pool, target->prop_targets);
+ hash_index;
+ hash_index = apr_hash_next(hash_index))
+ {
+ prop_patch_target_t *prop_target;
+
+ prop_target = svn__apr_hash_index_val(hash_index);
+
+ for (i = 0; i < prop_target->content->hunks->nelts; i++)
+ {
+ const hunk_info_t *hi;
+
+ svn_pool_clear(iterpool);
+
+ hi = APR_ARRAY_IDX(prop_target->content->hunks, i,
+ hunk_info_t *);
+
+ /* Don't notify on the hunk level for added or deleted props. */
+ if (prop_target->operation != svn_diff_op_added &&
+ prop_target->operation != svn_diff_op_deleted)
+ SVN_ERR(send_hunk_notification(hi, target, prop_target->name,
+ ctx, iterpool));
+ }
+ }
+ svn_pool_destroy(iterpool);
+ }
+
+ return SVN_NO_ERROR;
+}
+
+/* Apply a PATCH to a working copy at ABS_WC_PATH and put the result
+ * into temporary files, to be installed in the working copy later.
+ * Return information about the patch target in *PATCH_TARGET, allocated
+ * in RESULT_POOL. Use WC_CTX as the working copy context.
+ * STRIP_COUNT specifies the number of leading path components
+ * which should be stripped from target paths in the patch.
+ * REMOVE_TEMPFILES, PATCH_FUNC, and PATCH_BATON as in svn_client_patch().
+ * IGNORE_WHITESPACE tells whether whitespace should be considered when
+ * doing the matching.
+ * Call cancel CANCEL_FUNC with baton CANCEL_BATON to trigger cancellation.
+ * Do temporary allocations in SCRATCH_POOL. */
+static svn_error_t *
+apply_one_patch(patch_target_t **patch_target, svn_patch_t *patch,
+ const char *abs_wc_path, svn_wc_context_t *wc_ctx,
+ int strip_count,
+ svn_boolean_t ignore_whitespace,
+ svn_boolean_t remove_tempfiles,
+ svn_client_patch_func_t patch_func,
+ void *patch_baton,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ apr_pool_t *result_pool, apr_pool_t *scratch_pool)
+{
+ patch_target_t *target;
+ apr_pool_t *iterpool;
+ int i;
+ static const svn_linenum_t MAX_FUZZ = 2;
+ apr_hash_index_t *hash_index;
+
+ SVN_ERR(init_patch_target(&target, patch, abs_wc_path, wc_ctx, strip_count,
+ remove_tempfiles, result_pool, scratch_pool));
+ if (target->skipped)
+ {
+ *patch_target = target;
+ return SVN_NO_ERROR;
+ }
+
+ if (patch_func)
+ {
+ SVN_ERR(patch_func(patch_baton, &target->filtered,
+ target->canon_path_from_patchfile,
+ target->patched_path, target->reject_path,
+ scratch_pool));
+ if (target->filtered)
+ {
+ *patch_target = target;
+ return SVN_NO_ERROR;
+ }
+ }
+
+ iterpool = svn_pool_create(scratch_pool);
+ /* Match hunks. */
+ for (i = 0; i < patch->hunks->nelts; i++)
+ {
+ svn_diff_hunk_t *hunk;
+ hunk_info_t *hi;
+ svn_linenum_t fuzz = 0;
+
+ svn_pool_clear(iterpool);
+
+ if (cancel_func)
+ SVN_ERR(cancel_func(cancel_baton));
+
+ hunk = APR_ARRAY_IDX(patch->hunks, i, svn_diff_hunk_t *);
+
+ /* Determine the line the hunk should be applied at.
+ * If no match is found initially, try with fuzz. */
+ do
+ {
+ SVN_ERR(get_hunk_info(&hi, target, target->content, hunk, fuzz,
+ ignore_whitespace,
+ FALSE /* is_prop_hunk */,
+ cancel_func, cancel_baton,
+ result_pool, iterpool));
+ fuzz++;
+ }
+ while (hi->rejected && fuzz <= MAX_FUZZ && ! hi->already_applied);
+
+ APR_ARRAY_PUSH(target->content->hunks, hunk_info_t *) = hi;
+ }
+
+ /* Apply or reject hunks. */
+ for (i = 0; i < target->content->hunks->nelts; i++)
+ {
+ hunk_info_t *hi;
+
+ svn_pool_clear(iterpool);
+
+ if (cancel_func)
+ SVN_ERR(cancel_func(cancel_baton));
+
+ hi = APR_ARRAY_IDX(target->content->hunks, i, hunk_info_t *);
+ if (hi->already_applied)
+ continue;
+ else if (hi->rejected)
+ SVN_ERR(reject_hunk(target, target->content, hi->hunk,
+ NULL /* prop_name */,
+ iterpool));
+ else
+ SVN_ERR(apply_hunk(target, target->content, hi,
+ NULL /* prop_name */, iterpool));
+ }
+
+ if (target->kind_on_disk == svn_node_file)
+ {
+ /* Copy any remaining lines to target. */
+ SVN_ERR(copy_lines_to_target(target->content, 0, scratch_pool));
+ if (! target->content->eof)
+ {
+ /* We could not copy the entire target file to the temporary file,
+ * and would truncate the target if we copied the temporary file
+ * on top of it. Skip this target. */
+ target->skipped = TRUE;
+ }
+ }
+
+ /* Match property hunks. */
+ for (hash_index = apr_hash_first(scratch_pool, patch->prop_patches);
+ hash_index;
+ hash_index = apr_hash_next(hash_index))
+ {
+ svn_prop_patch_t *prop_patch;
+ const char *prop_name;
+ prop_patch_target_t *prop_target;
+
+ prop_name = svn__apr_hash_index_key(hash_index);
+ prop_patch = svn__apr_hash_index_val(hash_index);
+
+ if (! strcmp(prop_name, SVN_PROP_SPECIAL))
+ target->is_special = TRUE;
+
+ /* We'll store matched hunks in prop_content. */
+ prop_target = svn_hash_gets(target->prop_targets, prop_name);
+
+ for (i = 0; i < prop_patch->hunks->nelts; i++)
+ {
+ svn_diff_hunk_t *hunk;
+ hunk_info_t *hi;
+ svn_linenum_t fuzz = 0;
+
+ svn_pool_clear(iterpool);
+
+ if (cancel_func)
+ SVN_ERR(cancel_func(cancel_baton));
+
+ hunk = APR_ARRAY_IDX(prop_patch->hunks, i, svn_diff_hunk_t *);
+
+ /* Determine the line the hunk should be applied at.
+ * If no match is found initially, try with fuzz. */
+ do
+ {
+ SVN_ERR(get_hunk_info(&hi, target, prop_target->content,
+ hunk, fuzz,
+ ignore_whitespace,
+ TRUE /* is_prop_hunk */,
+ cancel_func, cancel_baton,
+ result_pool, iterpool));
+ fuzz++;
+ }
+ while (hi->rejected && fuzz <= MAX_FUZZ && ! hi->already_applied);
+
+ APR_ARRAY_PUSH(prop_target->content->hunks, hunk_info_t *) = hi;
+ }
+ }
+
+ /* Apply or reject property hunks. */
+ for (hash_index = apr_hash_first(scratch_pool, target->prop_targets);
+ hash_index;
+ hash_index = apr_hash_next(hash_index))
+ {
+ prop_patch_target_t *prop_target;
+
+ prop_target = svn__apr_hash_index_val(hash_index);
+
+ for (i = 0; i < prop_target->content->hunks->nelts; i++)
+ {
+ hunk_info_t *hi;
+
+ svn_pool_clear(iterpool);
+
+ hi = APR_ARRAY_IDX(prop_target->content->hunks, i,
+ hunk_info_t *);
+ if (hi->already_applied)
+ continue;
+ else if (hi->rejected)
+ SVN_ERR(reject_hunk(target, prop_target->content, hi->hunk,
+ prop_target->name,
+ iterpool));
+ else
+ SVN_ERR(apply_hunk(target, prop_target->content, hi,
+ prop_target->name,
+ iterpool));
+ }
+
+ if (prop_target->content->existed)
+ {
+ /* Copy any remaining lines to target. */
+ SVN_ERR(copy_lines_to_target(prop_target->content, 0,
+ scratch_pool));
+ if (! prop_target->content->eof)
+ {
+ /* We could not copy the entire target property to the
+ * temporary file, and would truncate the target if we
+ * copied the temporary file on top of it. Skip this target. */
+ target->skipped = TRUE;
+ }
+ }
+ }
+
+ svn_pool_destroy(iterpool);
+
+ if (!target->is_symlink)
+ {
+ /* Now close files we don't need any longer to get their contents
+ * flushed to disk.
+ * But we're not closing the reject file -- it still needed and
+ * will be closed later in write_out_rejected_hunks(). */
+ if (target->kind_on_disk == svn_node_file)
+ SVN_ERR(svn_io_file_close(target->file, scratch_pool));
+
+ SVN_ERR(svn_io_file_close(target->patched_file, scratch_pool));
+ }
+
+ if (! target->skipped)
+ {
+ apr_finfo_t working_file;
+ apr_finfo_t patched_file;
+
+ /* Get sizes of the patched temporary file and the working file.
+ * We'll need those to figure out whether we should delete the
+ * patched file. */
+ SVN_ERR(svn_io_stat(&patched_file, target->patched_path,
+ APR_FINFO_SIZE | APR_FINFO_LINK, scratch_pool));
+ if (target->kind_on_disk == svn_node_file)
+ SVN_ERR(svn_io_stat(&working_file, target->local_abspath,
+ APR_FINFO_SIZE | APR_FINFO_LINK, scratch_pool));
+ else
+ working_file.size = 0;
+
+ if (patched_file.size == 0 && working_file.size > 0)
+ {
+ /* If a unidiff removes all lines from a file, that usually
+ * means deletion, so we can confidently schedule the target
+ * for deletion. In the rare case where the unidiff was really
+ * meant to replace a file with an empty one, this may not
+ * be desirable. But the deletion can easily be reverted and
+ * creating an empty file manually is not exactly hard either. */
+ target->deleted = (target->db_kind == svn_node_file);
+ }
+ else if (patched_file.size == 0 && working_file.size == 0)
+ {
+ /* The target was empty or non-existent to begin with
+ * and no content was changed by patching.
+ * Report this as skipped if it didn't exist, unless in the special
+ * case of adding an empty file which has properties set on it or
+ * adding an empty file with a 'git diff' */
+ if (target->kind_on_disk == svn_node_none
+ && ! target->has_prop_changes
+ && ! target->added)
+ target->skipped = TRUE;
+ }
+ else if (patched_file.size > 0 && working_file.size == 0)
+ {
+ /* The patch has created a file. */
+ if (target->locally_deleted)
+ target->replaced = TRUE;
+ else if (target->db_kind == svn_node_none)
+ target->added = TRUE;
+ }
+ }
+
+ *patch_target = target;
+
+ return SVN_NO_ERROR;
+}
+
+/* Try to create missing parent directories for TARGET in the working copy
+ * rooted at ABS_WC_PATH, and add the parents to version control.
+ * If the parents cannot be created, mark the target as skipped.
+ * Use client context CTX. If DRY_RUN is true, do not create missing
+ * parents but issue notifications only.
+ * Use SCRATCH_POOL for temporary allocations. */
+static svn_error_t *
+create_missing_parents(patch_target_t *target,
+ const char *abs_wc_path,
+ svn_client_ctx_t *ctx,
+ svn_boolean_t dry_run,
+ apr_pool_t *scratch_pool)
+{
+ const char *local_abspath;
+ apr_array_header_t *components;
+ int present_components;
+ int i;
+ apr_pool_t *iterpool;
+
+ /* Check if we can safely create the target's parent. */
+ local_abspath = abs_wc_path;
+ components = svn_path_decompose(target->local_relpath, scratch_pool);
+ present_components = 0;
+ iterpool = svn_pool_create(scratch_pool);
+ for (i = 0; i < components->nelts - 1; i++)
+ {
+ const char *component;
+ svn_node_kind_t wc_kind, disk_kind;
+
+ svn_pool_clear(iterpool);
+
+ component = APR_ARRAY_IDX(components, i, const char *);
+ local_abspath = svn_dirent_join(local_abspath, component, scratch_pool);
+
+ SVN_ERR(svn_wc_read_kind2(&wc_kind, ctx->wc_ctx, local_abspath,
+ FALSE, TRUE, iterpool));
+
+ SVN_ERR(svn_io_check_path(local_abspath, &disk_kind, iterpool));
+
+ if (disk_kind == svn_node_file || wc_kind == svn_node_file)
+ {
+ /* on-disk files and missing files are obstructions */
+ target->skipped = TRUE;
+ break;
+ }
+ else if (disk_kind == svn_node_dir)
+ {
+ if (wc_kind == svn_node_dir)
+ present_components++;
+ else
+ {
+ target->skipped = TRUE;
+ break;
+ }
+ }
+ else if (wc_kind != svn_node_none)
+ {
+ /* Node is missing */
+ target->skipped = TRUE;
+ break;
+ }
+ else
+ {
+ /* It's not a file, it's not a dir...
+ Let's add a dir */
+ break;
+ }
+ }
+ if (! target->skipped)
+ {
+ local_abspath = abs_wc_path;
+ for (i = 0; i < present_components; i++)
+ {
+ const char *component;
+ component = APR_ARRAY_IDX(components, i, const char *);
+ local_abspath = svn_dirent_join(local_abspath,
+ component, scratch_pool);
+ }
+
+ if (!dry_run && present_components < components->nelts - 1)
+ SVN_ERR(svn_io_make_dir_recursively(
+ svn_dirent_join(
+ abs_wc_path,
+ svn_relpath_dirname(target->local_relpath,
+ scratch_pool),
+ scratch_pool),
+ scratch_pool));
+
+ for (i = present_components; i < components->nelts - 1; i++)
+ {
+ const char *component;
+
+ svn_pool_clear(iterpool);
+
+ component = APR_ARRAY_IDX(components, i, const char *);
+ local_abspath = svn_dirent_join(local_abspath, component,
+ scratch_pool);
+ if (dry_run)
+ {
+ if (ctx->notify_func2)
+ {
+ /* Just do notification. */
+ svn_wc_notify_t *notify;
+ notify = svn_wc_create_notify(local_abspath,
+ svn_wc_notify_add,
+ iterpool);
+ notify->kind = svn_node_dir;
+ ctx->notify_func2(ctx->notify_baton2, notify,
+ iterpool);
+ }
+ }
+ else
+ {
+ /* Create the missing component and add it
+ * to version control. Allow cancellation since we
+ * have not modified the working copy yet for this
+ * target. */
+
+ if (ctx->cancel_func)
+ SVN_ERR(ctx->cancel_func(ctx->cancel_baton));
+
+ SVN_ERR(svn_wc_add_from_disk2(ctx->wc_ctx, local_abspath,
+ NULL /*props*/,
+ ctx->notify_func2, ctx->notify_baton2,
+ iterpool));
+ }
+ }
+ }
+
+ svn_pool_destroy(iterpool);
+ return SVN_NO_ERROR;
+}
+
+/* Install a patched TARGET into the working copy at ABS_WC_PATH.
+ * Use client context CTX to retrieve WC_CTX, and possibly doing
+ * notifications. If DRY_RUN is TRUE, don't modify the working copy.
+ * Do temporary allocations in POOL. */
+static svn_error_t *
+install_patched_target(patch_target_t *target, const char *abs_wc_path,
+ svn_client_ctx_t *ctx, svn_boolean_t dry_run,
+ apr_pool_t *pool)
+{
+ if (target->deleted)
+ {
+ if (! dry_run)
+ {
+ /* Schedule the target for deletion. Suppress
+ * notification, we'll do it manually in a minute
+ * because we also need to notify during dry-run.
+ * Also suppress cancellation, because we'd rather
+ * notify about what we did before aborting. */
+ SVN_ERR(svn_wc_delete4(ctx->wc_ctx, target->local_abspath,
+ FALSE /* keep_local */, FALSE,
+ NULL, NULL, NULL, NULL, pool));
+ }
+ }
+ else
+ {
+ svn_node_kind_t parent_db_kind;
+ if (target->added || target->replaced)
+ {
+ const char *parent_abspath;
+
+ parent_abspath = svn_dirent_dirname(target->local_abspath,
+ pool);
+ /* If the target's parent directory does not yet exist
+ * we need to create it before we can copy the patched
+ * result in place. */
+ SVN_ERR(svn_wc_read_kind2(&parent_db_kind, ctx->wc_ctx,
+ parent_abspath, FALSE, FALSE, pool));
+
+ /* We can't add targets under nodes scheduled for delete, so add
+ a new directory if needed. */
+ if (parent_db_kind == svn_node_dir
+ || parent_db_kind == svn_node_file)
+ {
+ if (parent_db_kind != svn_node_dir)
+ target->skipped = TRUE;
+ else
+ {
+ svn_node_kind_t disk_kind;
+
+ SVN_ERR(svn_io_check_path(parent_abspath, &disk_kind, pool));
+ if (disk_kind != svn_node_dir)
+ target->skipped = TRUE;
+ }
+ }
+ else
+ SVN_ERR(create_missing_parents(target, abs_wc_path, ctx,
+ dry_run, pool));
+
+ }
+ else
+ {
+ svn_node_kind_t wc_kind;
+
+ /* The target should exist */
+ SVN_ERR(svn_wc_read_kind2(&wc_kind, ctx->wc_ctx,
+ target->local_abspath,
+ FALSE, FALSE, pool));
+
+ if (target->kind_on_disk == svn_node_none
+ || wc_kind != target->kind_on_disk)
+ {
+ target->skipped = TRUE;
+ }
+ }
+
+ if (! dry_run && ! target->skipped)
+ {
+ if (target->is_special)
+ {
+ svn_stream_t *stream;
+ svn_stream_t *patched_stream;
+
+ SVN_ERR(svn_stream_open_readonly(&patched_stream,
+ target->patched_path,
+ pool, pool));
+ SVN_ERR(svn_subst_create_specialfile(&stream,
+ target->local_abspath,
+ pool, pool));
+ SVN_ERR(svn_stream_copy3(patched_stream, stream,
+ ctx->cancel_func, ctx->cancel_baton,
+ pool));
+ }
+ else
+ {
+ svn_boolean_t repair_eol;
+
+ /* Copy the patched file on top of the target file.
+ * Always expand keywords in the patched file, but repair EOL
+ * only if svn:eol-style dictates a particular style. */
+ repair_eol = (target->content->eol_style ==
+ svn_subst_eol_style_fixed ||
+ target->content->eol_style ==
+ svn_subst_eol_style_native);
+
+ SVN_ERR(svn_subst_copy_and_translate4(
+ target->patched_path, target->local_abspath,
+ target->content->eol_str, repair_eol,
+ target->content->keywords,
+ TRUE /* expand */, FALSE /* special */,
+ ctx->cancel_func, ctx->cancel_baton, pool));
+ }
+
+ if (target->added || target->replaced)
+ {
+ /* The target file didn't exist previously,
+ * so add it to version control.
+ * Suppress notification, we'll do that later (and also
+ * during dry-run). Don't allow cancellation because
+ * we'd rather notify about what we did before aborting. */
+ SVN_ERR(svn_wc_add_from_disk2(ctx->wc_ctx, target->local_abspath,
+ NULL /*props*/,
+ NULL, NULL, pool));
+ }
+
+ /* Restore the target's executable bit if necessary. */
+ SVN_ERR(svn_io_set_file_executable(target->local_abspath,
+ target->executable,
+ FALSE, pool));
+ }
+ }
+
+ return SVN_NO_ERROR;
+}
+
+/* Write out rejected hunks, if any, to TARGET->REJECT_PATH. If DRY_RUN is
+ * TRUE, don't modify the working copy.
+ * Do temporary allocations in POOL.
+ */
+static svn_error_t *
+write_out_rejected_hunks(patch_target_t *target,
+ svn_boolean_t dry_run,
+ apr_pool_t *pool)
+{
+ SVN_ERR(svn_io_file_close(target->reject_file, pool));
+
+ if (! dry_run && (target->had_rejects || target->had_prop_rejects))
+ {
+ /* Write out rejected hunks, if any. */
+ SVN_ERR(svn_io_copy_file(target->reject_path,
+ apr_psprintf(pool, "%s.svnpatch.rej",
+ target->local_abspath),
+ FALSE, pool));
+ /* ### TODO mark file as conflicted. */
+ }
+ return SVN_NO_ERROR;
+}
+
+/* Install the patched properties for TARGET. Use client context CTX to
+ * retrieve WC_CTX. If DRY_RUN is TRUE, don't modify the working copy.
+ * Do temporary allocations in SCRATCH_POOL. */
+static svn_error_t *
+install_patched_prop_targets(patch_target_t *target,
+ svn_client_ctx_t *ctx, svn_boolean_t dry_run,
+ apr_pool_t *scratch_pool)
+{
+ apr_hash_index_t *hi;
+ apr_pool_t *iterpool;
+
+ iterpool = svn_pool_create(scratch_pool);
+
+ for (hi = apr_hash_first(scratch_pool, target->prop_targets);
+ hi;
+ hi = apr_hash_next(hi))
+ {
+ prop_patch_target_t *prop_target = svn__apr_hash_index_val(hi);
+ const svn_string_t *prop_val;
+ svn_error_t *err;
+
+ svn_pool_clear(iterpool);
+
+ if (ctx->cancel_func)
+ SVN_ERR(ctx->cancel_func(ctx->cancel_baton));
+
+ /* For a deleted prop we only set the value to NULL. */
+ if (prop_target->operation == svn_diff_op_deleted)
+ {
+ if (! dry_run)
+ SVN_ERR(svn_wc_prop_set4(ctx->wc_ctx, target->local_abspath,
+ prop_target->name, NULL, svn_depth_empty,
+ TRUE /* skip_checks */,
+ NULL /* changelist_filter */,
+ NULL, NULL /* cancellation */,
+ NULL, NULL /* notification */,
+ iterpool));
+ continue;
+ }
+
+ /* If the patch target doesn't exist yet, the patch wants to add an
+ * empty file with properties set on it. So create an empty file and
+ * add it to version control. But if the patch was in the 'git format'
+ * then the file has already been added.
+ *
+ * ### How can we tell whether the patch really wanted to create
+ * ### an empty directory? */
+ if (! target->has_text_changes
+ && target->kind_on_disk == svn_node_none
+ && ! target->added)
+ {
+ if (! dry_run)
+ {
+ SVN_ERR(svn_io_file_create(target->local_abspath, "",
+ scratch_pool));
+ SVN_ERR(svn_wc_add_from_disk2(ctx->wc_ctx, target->local_abspath,
+ NULL /*props*/,
+ /* suppress notification */
+ NULL, NULL,
+ iterpool));
+ }
+ target->added = TRUE;
+ }
+
+ /* Attempt to set the property, and reject all hunks if this
+ fails. If the property had a non-empty value, but now has
+ an empty one, we'll just delete the property altogether. */
+ if (prop_target->value && prop_target->value->len
+ && prop_target->patched_value && !prop_target->patched_value->len)
+ prop_val = NULL;
+ else
+ prop_val = svn_stringbuf__morph_into_string(prop_target->patched_value);
+
+ if (dry_run)
+ {
+ const svn_string_t *canon_propval;
+
+ err = svn_wc_canonicalize_svn_prop(&canon_propval,
+ prop_target->name,
+ prop_val, target->local_abspath,
+ target->db_kind,
+ TRUE, /* ### Skipping checks */
+ NULL, NULL,
+ iterpool);
+ }
+ else
+ {
+ err = svn_wc_prop_set4(ctx->wc_ctx, target->local_abspath,
+ prop_target->name, prop_val, svn_depth_empty,
+ TRUE /* skip_checks */,
+ NULL /* changelist_filter */,
+ NULL, NULL /* cancellation */,
+ NULL, NULL /* notification */,
+ iterpool);
+ }
+
+ if (err)
+ {
+ /* ### The errors which svn_wc_canonicalize_svn_prop() will
+ * ### return aren't documented. */
+ if (err->apr_err == SVN_ERR_ILLEGAL_TARGET ||
+ err->apr_err == SVN_ERR_NODE_UNEXPECTED_KIND ||
+ err->apr_err == SVN_ERR_IO_UNKNOWN_EOL ||
+ err->apr_err == SVN_ERR_BAD_MIME_TYPE ||
+ err->apr_err == SVN_ERR_CLIENT_INVALID_EXTERNALS_DESCRIPTION)
+ {
+ int i;
+
+ svn_error_clear(err);
+
+ for (i = 0; i < prop_target->content->hunks->nelts; i++)
+ {
+ hunk_info_t *hunk_info;
+
+ hunk_info = APR_ARRAY_IDX(prop_target->content->hunks,
+ i, hunk_info_t *);
+ hunk_info->rejected = TRUE;
+ SVN_ERR(reject_hunk(target, prop_target->content,
+ hunk_info->hunk, prop_target->name,
+ iterpool));
+ }
+ }
+ else
+ return svn_error_trace(err);
+ }
+
+ }
+
+ svn_pool_destroy(iterpool);
+
+ return SVN_NO_ERROR;
+}
+
+/* Baton for can_delete_callback */
+struct can_delete_baton_t
+{
+ svn_boolean_t must_keep;
+ const apr_array_header_t *targets_info;
+ const char *local_abspath;
+};
+
+/* Implements svn_wc_status_func4_t. */
+static svn_error_t *
+can_delete_callback(void *baton,
+ const char *abspath,
+ const svn_wc_status3_t *status,
+ apr_pool_t *pool)
+{
+ struct can_delete_baton_t *cb = baton;
+ int i;
+
+ switch(status->node_status)
+ {
+ case svn_wc_status_none:
+ case svn_wc_status_deleted:
+ return SVN_NO_ERROR;
+
+ default:
+ if (! strcmp(cb->local_abspath, abspath))
+ return SVN_NO_ERROR; /* Only interested in descendants */
+
+ for (i = 0; i < cb->targets_info->nelts; i++)
+ {
+ const patch_target_info_t *target_info =
+ APR_ARRAY_IDX(cb->targets_info, i, const patch_target_info_t *);
+
+ if (! strcmp(target_info->local_abspath, abspath))
+ {
+ if (target_info->deleted)
+ return SVN_NO_ERROR;
+
+ break; /* Cease invocation; must keep */
+ }
+ }
+
+ cb->must_keep = TRUE;
+
+ return svn_error_create(SVN_ERR_CEASE_INVOCATION, NULL, NULL);
+ }
+}
+
+static svn_error_t *
+check_ancestor_delete(const char *deleted_target,
+ apr_array_header_t *targets_info,
+ const char *apply_root,
+ svn_boolean_t dry_run,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ struct can_delete_baton_t cb;
+ svn_error_t *err;
+ apr_pool_t *iterpool = svn_pool_create(scratch_pool);
+
+ const char *dir_abspath = svn_dirent_dirname(deleted_target, scratch_pool);
+
+ while (svn_dirent_is_child(apply_root, dir_abspath, iterpool))
+ {
+ svn_pool_clear(iterpool);
+
+ cb.local_abspath = dir_abspath;
+ cb.must_keep = FALSE;
+ cb.targets_info = targets_info;
+
+ err = svn_wc_walk_status(ctx->wc_ctx, dir_abspath, svn_depth_infinity,
+ TRUE, FALSE, FALSE, NULL,
+ can_delete_callback, &cb,
+ ctx->cancel_func, ctx->cancel_baton,
+ iterpool);
+
+ if (err)
+ {
+ if (err->apr_err != SVN_ERR_CEASE_INVOCATION)
+ return svn_error_trace(err);
+
+ svn_error_clear(err);
+ }
+
+ if (cb.must_keep)
+ {
+ break;
+ }
+
+ if (! dry_run)
+ {
+ SVN_ERR(svn_wc_delete4(ctx->wc_ctx, dir_abspath, FALSE, FALSE,
+ ctx->cancel_func, ctx->cancel_baton,
+ NULL, NULL,
+ scratch_pool));
+ }
+
+ {
+ patch_target_info_t *pti = apr_pcalloc(result_pool, sizeof(*pti));
+
+ pti->local_abspath = apr_pstrdup(result_pool, dir_abspath);
+ pti->deleted = TRUE;
+
+ APR_ARRAY_PUSH(targets_info, patch_target_info_t *) = pti;
+ }
+
+
+ if (ctx->notify_func2)
+ {
+ svn_wc_notify_t *notify;
+
+ notify = svn_wc_create_notify(dir_abspath, svn_wc_notify_delete,
+ iterpool);
+ notify->kind = svn_node_dir;
+
+ ctx->notify_func2(ctx->notify_baton2, notify, iterpool);
+ }
+
+ /* And check if we must also delete the parent */
+ dir_abspath = svn_dirent_dirname(dir_abspath, scratch_pool);
+ }
+
+ svn_pool_destroy(iterpool);
+
+ return SVN_NO_ERROR;
+}
+
+/* This function is the main entry point into the patch code. */
+static svn_error_t *
+apply_patches(/* The path to the patch file. */
+ const char *patch_abspath,
+ /* The abspath to the working copy the patch should be applied to. */
+ const char *abs_wc_path,
+ /* Indicates whether we're doing a dry run. */
+ svn_boolean_t dry_run,
+ /* Number of leading components to strip from patch target paths. */
+ int strip_count,
+ /* Whether to apply the patch in reverse. */
+ svn_boolean_t reverse,
+ /* Whether to ignore whitespace when matching context lines. */
+ svn_boolean_t ignore_whitespace,
+ /* As in svn_client_patch(). */
+ svn_boolean_t remove_tempfiles,
+ /* As in svn_client_patch(). */
+ svn_client_patch_func_t patch_func,
+ void *patch_baton,
+ /* The client context. */
+ svn_client_ctx_t *ctx,
+ apr_pool_t *scratch_pool)
+{
+ svn_patch_t *patch;
+ apr_pool_t *iterpool;
+ svn_patch_file_t *patch_file;
+ apr_array_header_t *targets_info;
+
+ /* Try to open the patch file. */
+ SVN_ERR(svn_diff_open_patch_file(&patch_file, patch_abspath, scratch_pool));
+
+ /* Apply patches. */
+ targets_info = apr_array_make(scratch_pool, 0,
+ sizeof(patch_target_info_t *));
+ iterpool = svn_pool_create(scratch_pool);
+ do
+ {
+ svn_pool_clear(iterpool);
+
+ if (ctx->cancel_func)
+ SVN_ERR(ctx->cancel_func(ctx->cancel_baton));
+
+ SVN_ERR(svn_diff_parse_next_patch(&patch, patch_file,
+ reverse, ignore_whitespace,
+ iterpool, iterpool));
+ if (patch)
+ {
+ patch_target_t *target;
+
+ SVN_ERR(apply_one_patch(&target, patch, abs_wc_path,
+ ctx->wc_ctx, strip_count,
+ ignore_whitespace, remove_tempfiles,
+ patch_func, patch_baton,
+ ctx->cancel_func, ctx->cancel_baton,
+ iterpool, iterpool));
+ if (! target->filtered)
+ {
+ /* Save info we'll still need when we're done patching. */
+ patch_target_info_t *target_info =
+ apr_pcalloc(scratch_pool, sizeof(patch_target_info_t));
+ target_info->local_abspath = apr_pstrdup(scratch_pool,
+ target->local_abspath);
+ target_info->deleted = target->deleted;
+
+ if (! target->skipped)
+ {
+ APR_ARRAY_PUSH(targets_info,
+ patch_target_info_t *) = target_info;
+
+ if (target->has_text_changes
+ || target->added
+ || target->deleted)
+ SVN_ERR(install_patched_target(target, abs_wc_path,
+ ctx, dry_run, iterpool));
+
+ if (target->has_prop_changes && (!target->deleted))
+ SVN_ERR(install_patched_prop_targets(target, ctx,
+ dry_run, iterpool));
+
+ SVN_ERR(write_out_rejected_hunks(target, dry_run, iterpool));
+ }
+ SVN_ERR(send_patch_notification(target, ctx, iterpool));
+
+ if (target->deleted && !target->skipped)
+ {
+ SVN_ERR(check_ancestor_delete(target_info->local_abspath,
+ targets_info, abs_wc_path,
+ dry_run, ctx,
+ scratch_pool, iterpool));
+ }
+ }
+ }
+ }
+ while (patch);
+
+ SVN_ERR(svn_diff_close_patch_file(patch_file, iterpool));
+ svn_pool_destroy(iterpool);
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_client_patch(const char *patch_abspath,
+ const char *wc_dir_abspath,
+ svn_boolean_t dry_run,
+ int strip_count,
+ svn_boolean_t reverse,
+ svn_boolean_t ignore_whitespace,
+ svn_boolean_t remove_tempfiles,
+ svn_client_patch_func_t patch_func,
+ void *patch_baton,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *scratch_pool)
+{
+ svn_node_kind_t kind;
+
+ if (strip_count < 0)
+ return svn_error_create(SVN_ERR_INCORRECT_PARAMS, NULL,
+ _("strip count must be positive"));
+
+ if (svn_path_is_url(wc_dir_abspath))
+ return svn_error_createf(SVN_ERR_ILLEGAL_TARGET, NULL,
+ _("'%s' is not a local path"),
+ svn_dirent_local_style(wc_dir_abspath,
+ scratch_pool));
+
+ SVN_ERR(svn_io_check_path(patch_abspath, &kind, scratch_pool));
+ if (kind == svn_node_none)
+ return svn_error_createf(SVN_ERR_ILLEGAL_TARGET, NULL,
+ _("'%s' does not exist"),
+ svn_dirent_local_style(patch_abspath,
+ scratch_pool));
+ if (kind != svn_node_file)
+ return svn_error_createf(SVN_ERR_ILLEGAL_TARGET, NULL,
+ _("'%s' is not a file"),
+ svn_dirent_local_style(patch_abspath,
+ scratch_pool));
+
+ SVN_ERR(svn_io_check_path(wc_dir_abspath, &kind, scratch_pool));
+ if (kind == svn_node_none)
+ return svn_error_createf(SVN_ERR_ILLEGAL_TARGET, NULL,
+ _("'%s' does not exist"),
+ svn_dirent_local_style(wc_dir_abspath,
+ scratch_pool));
+ if (kind != svn_node_dir)
+ return svn_error_createf(SVN_ERR_ILLEGAL_TARGET, NULL,
+ _("'%s' is not a directory"),
+ svn_dirent_local_style(wc_dir_abspath,
+ scratch_pool));
+
+ SVN_WC__CALL_WITH_WRITE_LOCK(
+ apply_patches(patch_abspath, wc_dir_abspath, dry_run, strip_count,
+ reverse, ignore_whitespace, remove_tempfiles,
+ patch_func, patch_baton, ctx, scratch_pool),
+ ctx->wc_ctx, wc_dir_abspath, FALSE /* lock_anchor */, scratch_pool);
+ return SVN_NO_ERROR;
+}
OpenPOWER on IntegriCloud