You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@subversion.apache.org by hw...@apache.org on 2010/08/11 18:43:31 UTC
svn commit: r984468 [6/25] - in /subversion/branches/ignore-mergeinfo: ./
build/ build/generator/ build/generator/templates/ notes/
notes/tree-conflicts/ notes/wc-ng/ subversion/bindings/javahl/native/
subversion/bindings/javahl/src/org/apache/subversi...
Modified: subversion/branches/ignore-mergeinfo/subversion/libsvn_client/patch.c
URL: http://svn.apache.org/viewvc/subversion/branches/ignore-mergeinfo/subversion/libsvn_client/patch.c?rev=984468&r1=984467&r2=984468&view=diff
==============================================================================
--- subversion/branches/ignore-mergeinfo/subversion/libsvn_client/patch.c (original)
+++ subversion/branches/ignore-mergeinfo/subversion/libsvn_client/patch.c Wed Aug 11 16:43:22 2010
@@ -46,7 +46,7 @@
#include "private/svn_wc_private.h"
#include "private/svn_dep_compat.h"
-typedef struct {
+typedef struct hunk_info_t {
/* The hunk. */
const svn_hunk_t *hunk;
@@ -56,33 +56,18 @@ typedef struct {
/* 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. */
int fuzz;
} hunk_info_t;
-typedef struct {
- /* The patch being applied. */
- const svn_patch_t *patch;
-
- /* 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. */
- char *local_abspath;
-
- /* The target file, read-only, seekable. This is NULL in case the target
- * file did not exist prior to patch application. */
- apr_file_t *file;
-
+/* A struct carrying the information related to the content of a target, be it
+ * a property or the the text of a file. */
+typedef struct target_content_info_t {
/* A stream to read lines form the target file. This is NULL in case
* the target file did not exist prior to patch application. */
svn_stream_t *stream;
@@ -94,19 +79,10 @@ typedef struct {
* so EOL transformation and keyword contraction is done transparently. */
svn_stream_t *patched;
- /* The patched stream, without EOL transformation and keyword expansion. */
- svn_stream_t *patched_raw;
-
- /* Path to the temporary file underlying the result stream. */
- const char *patched_path;
-
/* The reject stream, write-only, not seekable.
* Hunks that are rejected will be written to this stream. */
svn_stream_t *reject;
- /* Path to the temporary file underlying the reject stream. */
- const char *reject_path;
-
/* The line last read from the target file. */
svn_linenum_t current_line;
@@ -126,6 +102,61 @@ typedef struct {
/* 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 target. */
+ svn_boolean_t eof;
+
+ /* The keywords of the target. */
+ apr_hash_t *keywords;
+
+ /* The pool the target_info is allocated in. */
+ apr_pool_t *pool;
+} target_content_info_t;
+
+typedef struct prop_patch_target_t {
+
+ /* The name of the property */
+ const char *name;
+
+ /* All the information that is specific to the content of the property. */
+ target_content_info_t *content_info;
+
+ /* Path to the temporary file underlying the result stream. */
+ const char *patched_path;
+
+ /* 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, seekable. This is NULL in case the target
+ * file did not exist prior to patch application. */
+ apr_file_t *file;
+
+ /* Path to the temporary file underlying the result stream. */
+ const char *patched_path;
+
+ /* Path to the temporary file underlying the reject stream. */
+ 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;
@@ -136,9 +167,6 @@ typedef struct {
/* True if the target was locally deleted prior to patching. */
svn_boolean_t locally_deleted;
- /* True if end-of-file was reached while reading from the target. */
- svn_boolean_t eof;
-
/* True if the target had to be skipped for some reason. */
svn_boolean_t skipped;
@@ -148,6 +176,9 @@ typedef struct {
/* True if at least one hunk was rejected. */
svn_boolean_t had_rejects;
+ /* True if atleast 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;
@@ -166,8 +197,17 @@ typedef struct {
/* True if the target has the executable bit set. */
svn_boolean_t executable;
- /* The keywords of the target. */
- apr_hash_t *keywords;
+ /* True if the patch changes the text of the target */
+ svn_boolean_t has_text_changes;
+
+ /* True if the patch changes any of the properties of the target */
+ svn_boolean_t has_prop_changes;
+
+ /* All the information that is specific to the content of the target. */
+ target_content_info_t *content_info;
+
+ /* A hash table of prop_patch_target_t objects keyed by property names. */
+ apr_hash_t *prop_targets;
/* The pool the target is allocated in. */
apr_pool_t *pool;
@@ -177,7 +217,7 @@ typedef struct {
/* 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 {
+typedef struct patch_target_info_t {
const char *local_abspath;
svn_boolean_t deleted;
} patch_target_info_t;
@@ -216,6 +256,62 @@ strip_path(const char **result, const ch
return SVN_NO_ERROR;
}
+/* Obtain KEYWORDS, EOL_STYLE and EOL_STR for LOCAL_ABSPATH, from WC_CTX.
+ * 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;
+
+ /* Handle svn:keyword and svn:eol-style properties. */
+ SVN_ERR(svn_wc_prop_list2(&props, wc_ctx, local_abspath,
+ scratch_pool, scratch_pool));
+ keywords_val = apr_hash_get(props, SVN_PROP_KEYWORDS,
+ APR_HASH_KEY_STRING);
+ if (keywords_val)
+ {
+ svn_revnum_t changed_rev;
+ apr_time_t changed_date;
+ const char *rev_str;
+ const char *author;
+ const char *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_subst_build_keywords2(keywords,
+ keywords_val->data,
+ rev_str, url, changed_date,
+ author, result_pool));
+ }
+
+ eol_style_val = apr_hash_get(props, SVN_PROP_EOL_STYLE,
+ APR_HASH_KEY_STRING);
+ 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
@@ -226,23 +322,33 @@ strip_path(const char **result, const ch
* 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.
- * Use RESULT_POOL for allocations of fields in TARGET. */
+ * PROP_CHANGES_ONLY is used for determining if the target path is allowed
+ * to be a dir.
+ * 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 *local_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;
+ const char *full_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);
- if (target->canon_path_from_patchfile[0] == '\0')
+
+ /* We allow properties to be set on the wc root dir.
+ * ### Do we need to check for empty paths here, shouldn't the parser
+ * ### guarentee that the paths returned are non-empty? */
+ if (! prop_changes_only && target->canon_path_from_patchfile[0] == '\0')
{
/* An empty patch target path? What gives? Skip this. */
target->skipped = TRUE;
@@ -261,6 +367,7 @@ resolve_target_path(patch_target_t *targ
{
target->local_relpath = svn_dirent_is_child(local_abspath, stripped_path,
result_pool);
+
if (! target->local_relpath)
{
/* The target path is either outside of the working copy
@@ -278,8 +385,11 @@ resolve_target_path(patch_target_t *targ
/* 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. */
- if (! svn_dirent_is_under_root(&target->local_abspath, local_abspath,
- target->local_relpath, result_pool))
+ SVN_ERR(svn_dirent_is_under_root(&under_root,
+ &full_path, local_abspath,
+ target->local_relpath, result_pool));
+
+ if (! under_root)
{
/* The target path is outside of the working copy. Skip it. */
target->skipped = TRUE;
@@ -287,9 +397,12 @@ resolve_target_path(patch_target_t *targ
return SVN_NO_ERROR;
}
+ SVN_ERR(svn_dirent_get_absolute(&target->local_abspath, full_path,
+ result_pool));
+
/* Skip things we should not be messing with. */
err = svn_wc_status3(&status, wc_ctx, target->local_abspath,
- result_pool, scratch_pool);
+ result_pool, scratch_pool);
if (err)
{
if (err->apr_err == SVN_ERR_WC_PATH_NOT_FOUND)
@@ -323,14 +436,97 @@ resolve_target_path(patch_target_t *targ
}
SVN_ERR(svn_wc_read_kind(&target->db_kind, wc_ctx, target->local_abspath,
FALSE, scratch_pool));
- if (target->db_kind == svn_node_dir)
- target->skipped = TRUE;
- /* ### We cannot yet replace a locally deleted dir with a file,
- * ### but some day we might want to allow it. */
+
+ /* If the target is a version directory not missing from disk,
+ * and there are only property changes in the patch, we accept
+ * a directory target. Else, we skip directories. */
+ if (target->db_kind == svn_node_dir && ! prop_changes_only)
+ {
+ /* ### We cannot yet replace a locally deleted dir with a file,
+ * ### but some day we might want to allow it. */
+ target->skipped = TRUE;
+ return SVN_NO_ERROR;
+ }
/* ### Shouldn't libsvn_wc flag an obstruction in this case? */
if (target->locally_deleted && target->kind_on_disk != svn_node_none)
- target->skipped = TRUE;
+ {
+ target->skipped = TRUE;
+ return SVN_NO_ERROR;
+ }
+
+ return SVN_NO_ERROR;
+}
+
+/* Initialize a PROP_TARGET structure for PROP_NAME. Use working copy
+ * context WC_CTX. Use the REJECT stream that is shared with all content
+ * info structures.
+ * REMOVE_TEMPFILES as in svn_client_patch().
+ */
+static svn_error_t *
+init_prop_target(prop_patch_target_t **prop_target,
+ const char *prop_name,
+ svn_diff_operation_kind_t operation,
+ svn_stream_t *reject,
+ svn_boolean_t remove_tempfiles,
+ 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_info_t *content_info;
+ const svn_string_t *value;
+ const char *patched_path;
+ svn_error_t *err;
+
+ content_info = apr_pcalloc(result_pool, sizeof(*content_info));
+
+ /* All other fields in are FALSE or NULL due to apr_pcalloc().*/
+ content_info->current_line = 1;
+ content_info->eol_style = svn_subst_eol_style_none;
+ content_info->lines = apr_array_make(result_pool, 0,
+ sizeof(svn_stream_mark_t *));
+ content_info->hunks = apr_array_make(result_pool, 0, sizeof(hunk_info_t *));
+ content_info->keywords = apr_hash_make(result_pool);
+ content_info->reject = reject;
+ content_info->pool = result_pool;
+
+ new_prop_target = apr_palloc(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_info = content_info;
+
+ 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_return(err);
+ }
+
+ if (value)
+ {
+ /* We assume that a property is small enough to be kept in memory during
+ * the patch process. */
+ content_info->stream = svn_stream_from_string(value, result_pool);
+ }
+
+ /* Create a temporary file to write the patched result to. For properties,
+ * we don't have to worrry about different eol-style. */
+ SVN_ERR(svn_stream_open_unique(&content_info->patched,
+ &patched_path, NULL,
+ remove_tempfiles ?
+ svn_io_file_del_on_pool_cleanup :
+ svn_io_file_del_none,
+ result_pool, scratch_pool));
+
+ new_prop_target->patched_path = patched_path;
+ *prop_target = new_prop_target;
return SVN_NO_ERROR;
}
@@ -339,8 +535,10 @@ resolve_target_path(patch_target_t *targ
* 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.
- * Upon success, allocate the patch target structure in RESULT_POOL.
- * Else, set *target to NULL.
+ * 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.
* REMOVE_TEMPFILES as in svn_client_patch().
* Use SCRATCH_POOL for all other allocations. */
static svn_error_t *
@@ -352,32 +550,57 @@ init_patch_target(patch_target_t **patch
apr_pool_t *result_pool, apr_pool_t *scratch_pool)
{
patch_target_t *target;
+ target_content_info_t *content_info;
+ svn_boolean_t has_prop_changes = FALSE;
+ svn_boolean_t prop_changes_only = FALSE;
+
+ {
+ apr_hash_index_t *hi;
+
+ /* ### Should we use an iterpool here? */
+ 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_info = apr_pcalloc(result_pool, sizeof(*content_info));
+
+ /* All other fields in content_info are FALSE or NULL due to apr_pcalloc().*/
+ content_info->current_line = 1;
+ content_info->eol_style = svn_subst_eol_style_none;
+ content_info->lines = apr_array_make(result_pool, 0,
+ sizeof(svn_stream_mark_t *));
+ content_info->hunks = apr_array_make(result_pool, 0, sizeof(hunk_info_t *));
+ content_info->keywords = apr_hash_make(result_pool);
+ content_info->pool = result_pool;
target = apr_pcalloc(result_pool, sizeof(*target));
- /* All other fields are FALSE or NULL due to apr_pcalloc(). */
- target->patch = patch;
- target->current_line = 1;
- target->eol_style = svn_subst_eol_style_none;
- target->lines = apr_array_make(result_pool, 0,
- sizeof(svn_stream_mark_t *));
- target->hunks = apr_array_make(result_pool, 0, sizeof(hunk_info_t *));
+ /* 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_info = content_info;
+ target->prop_targets = apr_hash_make(result_pool);
target->pool = result_pool;
SVN_ERR(resolve_target_path(target, patch->new_filename,
- base_dir, strip_count, wc_ctx,
- result_pool, scratch_pool));
+ base_dir, strip_count, prop_changes_only,
+ wc_ctx, result_pool, scratch_pool));
if (! target->skipped)
{
- apr_hash_t *props;
- svn_string_t *keywords_val;
- svn_string_t *eol_style_val;
const char *diff_header;
svn_boolean_t repair_eol;
apr_size_t len;
-
+ svn_stream_t *patched_raw;
if (target->kind_on_disk == svn_node_file)
{
@@ -386,48 +609,9 @@ init_patch_target(patch_target_t **patch
APR_READ | APR_BINARY | APR_BUFFERED,
APR_OS_DEFAULT, result_pool));
- /* Handle svn:keyword and svn:eol-style properties. */
- SVN_ERR(svn_wc_prop_list2(&props, wc_ctx, target->local_abspath,
- scratch_pool, scratch_pool));
- keywords_val = apr_hash_get(props, SVN_PROP_KEYWORDS,
- APR_HASH_KEY_STRING);
- if (keywords_val)
- {
- svn_revnum_t changed_rev;
- apr_time_t changed_date;
- const char *rev_str;
- const char *author;
- const char *url;
-
- SVN_ERR(svn_wc__node_get_changed_info(&changed_rev,
- &changed_date,
- &author, wc_ctx,
- target->local_abspath,
- scratch_pool,
- scratch_pool));
- rev_str = apr_psprintf(scratch_pool, "%"SVN_REVNUM_T_FMT,
- changed_rev);
- SVN_ERR(svn_wc__node_get_url(&url, wc_ctx,
- target->local_abspath,
- scratch_pool, scratch_pool));
- SVN_ERR(svn_subst_build_keywords2(&target->keywords,
- keywords_val->data,
- rev_str, url, changed_date,
- author, result_pool));
- }
-
- eol_style_val = apr_hash_get(props, SVN_PROP_EOL_STYLE,
- APR_HASH_KEY_STRING);
- if (eol_style_val)
- {
- svn_subst_eol_style_from_value(&target->eol_style,
- &target->eol_str,
- eol_style_val->data);
- }
-
/* Create a stream to read from the target. */
- target->stream = svn_stream_from_aprfile2(target->file,
- FALSE, result_pool);
+ content_info->stream = svn_stream_from_aprfile2(target->file,
+ FALSE, result_pool);
/* Also check the file for local mods and the Xbit. */
SVN_ERR(svn_wc_text_modified_p2(&target->local_mods, wc_ctx,
@@ -436,10 +620,18 @@ init_patch_target(patch_target_t **patch
SVN_ERR(svn_io_is_file_executable(&target->executable,
target->local_abspath,
scratch_pool));
+
+ SVN_ERR(obtain_eol_and_keywords_for_file(&content_info->keywords,
+ &content_info->eol_style,
+ &content_info->eol_str,
+ wc_ctx,
+ target->local_abspath,
+ result_pool,
+ scratch_pool));
}
/* Create a temporary file to write the patched result to. */
- SVN_ERR(svn_stream_open_unique(&target->patched_raw,
+ SVN_ERR(svn_stream_open_unique(&patched_raw,
&target->patched_path, NULL,
remove_tempfiles ?
svn_io_file_del_on_pool_cleanup :
@@ -448,16 +640,16 @@ init_patch_target(patch_target_t **patch
/* Expand keywords in the patched file.
* Repair newlines if svn:eol-style dictates a particular style. */
- repair_eol = (target->eol_style == svn_subst_eol_style_fixed ||
- target->eol_style == svn_subst_eol_style_native);
- target->patched = svn_subst_stream_translated(
- target->patched_raw, target->eol_str, repair_eol,
- target->keywords, TRUE, result_pool);
+ repair_eol = (content_info->eol_style == svn_subst_eol_style_fixed
+ || content_info->eol_style == svn_subst_eol_style_native);
+ content_info->patched = svn_subst_stream_translated(
+ patched_raw, content_info->eol_str, repair_eol,
+ content_info->keywords, TRUE, result_pool);
/* We'll also need a stream to write rejected hunks to.
* We don't expand keywords, nor normalise line-endings,
* in reject files. */
- SVN_ERR(svn_stream_open_unique(&target->reject,
+ SVN_ERR(svn_stream_open_unique(&content_info->reject,
&target->reject_path, NULL,
remove_tempfiles ?
svn_io_file_del_on_pool_cleanup :
@@ -471,19 +663,47 @@ init_patch_target(patch_target_t **patch
target->canon_path_from_patchfile,
APR_EOL_STR);
len = strlen(diff_header);
- SVN_ERR(svn_stream_write(target->reject, diff_header, &len));
+ SVN_ERR(svn_stream_write(content_info->reject, diff_header, &len));
+
+ /* Time for the 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;
+
+ /* Obtain info about this property */
+ SVN_ERR(init_prop_target(&prop_target,
+ prop_name,
+ prop_patch->operation,
+ content_info->reject,
+ remove_tempfiles,
+ wc_ctx, target->local_abspath,
+ result_pool, scratch_pool));
+
+ /* Store the info */
+ apr_hash_set(target->prop_targets, prop_name,
+ APR_HASH_KEY_STRING, prop_target);
+ }
+ }
}
*patch_target = target;
return SVN_NO_ERROR;
}
-/* Read a *LINE from TARGET. If the line has not been read before
- * mark the line in TARGET->LINES. Allocate *LINE in RESULT_POOL.
+/* Read a *LINE from CONTENT_INFO. If the line has not been read before
+ * mark the line in CONTENT_INFO->LINES. Allocate *LINE in RESULT_POOL.
* Do temporary allocations in SCRATCH_POOL.
*/
static svn_error_t *
-read_line(patch_target_t *target,
+read_line(target_content_info_t *content_info,
const char **line,
apr_pool_t *scratch_pool,
apr_pool_t *result_pool)
@@ -491,43 +711,44 @@ read_line(patch_target_t *target,
svn_stringbuf_t *line_raw;
const char *eol_str;
- if (target->eof)
+ if (content_info->eof)
{
*line = "";
return SVN_NO_ERROR;
}
- SVN_ERR_ASSERT(target->current_line <= target->lines->nelts + 1);
- if (target->current_line == target->lines->nelts + 1)
+ SVN_ERR_ASSERT(content_info->current_line <= content_info->lines->nelts + 1);
+ if (content_info->current_line == content_info->lines->nelts + 1)
{
svn_stream_mark_t *mark;
- SVN_ERR(svn_stream_mark(target->stream, &mark, target->pool));
- APR_ARRAY_PUSH(target->lines, svn_stream_mark_t *) = mark;
+ SVN_ERR(svn_stream_mark(content_info->stream, &mark,
+ content_info->pool));
+ APR_ARRAY_PUSH(content_info->lines, svn_stream_mark_t *) = mark;
}
- SVN_ERR(svn_stream_readline_detect_eol(target->stream, &line_raw,
- &eol_str, &target->eof,
+ SVN_ERR(svn_stream_readline_detect_eol(content_info->stream, &line_raw,
+ &eol_str, &content_info->eof,
scratch_pool));
- if (target->eol_style == svn_subst_eol_style_none)
- target->eol_str = eol_str;
+ if (content_info->eol_style == svn_subst_eol_style_none)
+ content_info->eol_str = eol_str;
/* Contract keywords. */
SVN_ERR(svn_subst_translate_cstring2(line_raw->data, line,
NULL, FALSE,
- target->keywords, FALSE,
+ content_info->keywords, FALSE,
result_pool));
- if (! target->eof)
- target->current_line++;
+ if (! content_info->eof)
+ content_info->current_line++;
return SVN_NO_ERROR;
}
-/* Seek to the specified LINE in TARGET.
- * Mark any lines not read before in TARGET->LINES.
+/* Seek to the specified LINE in CONTENT_INFO.
+ * Mark any lines not read before in CONTENT_INFO->LINES.
* Do temporary allocations in SCRATCH_POOL.
*/
static svn_error_t *
-seek_to_line(patch_target_t *target, svn_linenum_t line,
+seek_to_line(target_content_info_t *content_info, svn_linenum_t line,
apr_pool_t *scratch_pool)
{
svn_linenum_t saved_line;
@@ -535,51 +756,53 @@ seek_to_line(patch_target_t *target, svn
SVN_ERR_ASSERT(line > 0);
- if (line == target->current_line)
+ if (line == content_info->current_line)
return SVN_NO_ERROR;
- saved_line = target->current_line;
- saved_eof = target->eof;
+ saved_line = content_info->current_line;
+ saved_eof = content_info->eof;
- if (line <= target->lines->nelts)
+ if (line <= content_info->lines->nelts)
{
svn_stream_mark_t *mark;
- mark = APR_ARRAY_IDX(target->lines, line - 1, svn_stream_mark_t *);
- SVN_ERR(svn_stream_seek(target->stream, mark));
- target->current_line = line;
+ mark = APR_ARRAY_IDX(content_info->lines, line - 1, svn_stream_mark_t *);
+ SVN_ERR(svn_stream_seek(content_info->stream, mark));
+ content_info->current_line = line;
}
else
{
const char *dummy;
apr_pool_t *iterpool = svn_pool_create(scratch_pool);
- while (! target->eof && target->current_line < line)
+ while (! content_info->eof && content_info->current_line < line)
{
svn_pool_clear(iterpool);
- SVN_ERR(read_line(target, &dummy, iterpool, iterpool));
+ SVN_ERR(read_line(content_info, &dummy, iterpool, iterpool));
}
svn_pool_destroy(iterpool);
}
/* After seeking backwards from EOF position clear EOF indicator. */
- if (saved_eof && saved_line > target->current_line)
- target->eof = FALSE;
+ if (saved_eof && saved_line > content_info->current_line)
+ content_info->eof = FALSE;
return SVN_NO_ERROR;
}
/* Indicate in *MATCHED whether the original text of HUNK matches the patch
- * TARGET 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
+ * CONTENT_INFO 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
- * TARGET->CURRENT_LINE nor the file offset in the target file will have
- * changed. HUNK->ORIGINAL_TEXT will be reset. Do temporary allocations in
- * POOL. */
+ * CONTENT_INFO->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, patch_target_t *target,
+match_hunk(svn_boolean_t *matched, target_content_info_t *content_info,
const svn_hunk_t *hunk, int fuzz,
- svn_boolean_t ignore_whitespace, apr_pool_t *pool)
+ svn_boolean_t ignore_whitespace,
+ svn_boolean_t match_modified, apr_pool_t *pool)
{
svn_stringbuf_t *hunk_line;
const char *target_line;
@@ -588,16 +811,25 @@ match_hunk(svn_boolean_t *matched, patch
svn_boolean_t hunk_eof;
svn_boolean_t lines_matched;
apr_pool_t *iterpool;
+ svn_linenum_t original_length;
+ svn_linenum_t leading_context;
+ svn_linenum_t trailing_context;
*matched = FALSE;
- if (target->eof)
+ if (content_info->eof)
return SVN_NO_ERROR;
- saved_line = target->current_line;
+ saved_line = content_info->current_line;
lines_read = 0;
lines_matched = FALSE;
- SVN_ERR(svn_stream_reset(hunk->original_text));
+ original_length = svn_diff_hunk_get_original_length(hunk);
+ leading_context = svn_diff_hunk_get_leading_context(hunk);
+ trailing_context = svn_diff_hunk_get_trailing_context(hunk);
+ if (match_modified)
+ SVN_ERR(svn_diff_hunk_reset_modified_text(hunk));
+ else
+ SVN_ERR(svn_diff_hunk_reset_original_text(hunk));
iterpool = svn_pool_create(pool);
do
{
@@ -605,23 +837,29 @@ match_hunk(svn_boolean_t *matched, patch
svn_pool_clear(iterpool);
- SVN_ERR(svn_stream_readline_detect_eol(hunk->original_text,
- &hunk_line, NULL,
- &hunk_eof, 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,
- target->keywords, FALSE,
+ content_info->keywords, FALSE,
iterpool));
lines_read++;
- SVN_ERR(read_line(target, &target_line, iterpool, iterpool));
+ SVN_ERR(read_line(content_info, &target_line, iterpool, iterpool));
if (! hunk_eof)
{
- if (lines_read <= fuzz && hunk->leading_context > fuzz)
+ if (lines_read <= fuzz && leading_context > fuzz)
lines_matched = TRUE;
- else if (lines_read > hunk->original_length - fuzz &&
- hunk->trailing_context > fuzz)
+ else if (lines_read > original_length - fuzz &&
+ trailing_context > fuzz)
lines_matched = TRUE;
else
{
@@ -642,29 +880,39 @@ match_hunk(svn_boolean_t *matched, patch
}
}
}
- while (lines_matched && ! (hunk_eof || target->eof));
+ while (lines_matched && ! (hunk_eof || content_info->eof));
if (hunk_eof)
*matched = lines_matched;
- else if (target->eof)
+ else if (content_info->eof)
{
/* If the target has no newline at end-of-file, we get an EOF
* indication for the target earlier than we do get it for the hunk. */
- SVN_ERR(svn_stream_readline_detect_eol(hunk->original_text, &hunk_line,
- NULL, &hunk_eof, iterpool));
- if (hunk_line->len == 0 && hunk_eof)
+ 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));
+
+ /* When comparing modified text we require that all lines match, else
+ * a hunk that adds a newline at the end will be treated as already
+ * applied even if it isn't. */
+ if (! match_modified && hunk_line->len == 0 && hunk_eof)
*matched = lines_matched;
else
*matched = FALSE;
}
- SVN_ERR(seek_to_line(target, saved_line, iterpool));
+ SVN_ERR(seek_to_line(content_info, saved_line, iterpool));
svn_pool_destroy(iterpool);
return SVN_NO_ERROR;
}
-/* Scan lines of TARGET for a match of the original text of HUNK,
+/* Scan lines of CONTENT_INFO 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.
@@ -674,13 +922,17 @@ match_hunk(svn_boolean_t *matched, patch
* If the hunk matched multiple times, and MATCH_FIRST is FALSE,
* return the line number at which the last match occured 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, patch_target_t *target,
+scan_for_match(svn_linenum_t *matched_line,
+ target_content_info_t *content_info,
const svn_hunk_t *hunk, svn_boolean_t match_first,
svn_linenum_t upper_line, int fuzz,
svn_boolean_t ignore_whitespace,
+ svn_boolean_t match_modified,
svn_cancel_func_t cancel_func, void *cancel_baton,
apr_pool_t *pool)
{
@@ -688,8 +940,8 @@ scan_for_match(svn_linenum_t *matched_li
*matched_line = 0;
iterpool = svn_pool_create(pool);
- while ((target->current_line < upper_line || upper_line == 0) &&
- ! target->eof)
+ while ((content_info->current_line < upper_line || upper_line == 0) &&
+ ! content_info->eof)
{
svn_boolean_t matched;
int i;
@@ -699,117 +951,278 @@ scan_for_match(svn_linenum_t *matched_li
if (cancel_func)
SVN_ERR((cancel_func)(cancel_baton));
- SVN_ERR(match_hunk(&matched, target, hunk, fuzz, ignore_whitespace,
- iterpool));
+ SVN_ERR(match_hunk(&matched, content_info, hunk, fuzz, ignore_whitespace,
+ match_modified, iterpool));
if (matched)
{
svn_boolean_t taken = FALSE;
/* Don't allow hunks to match at overlapping locations. */
- for (i = 0; i < target->hunks->nelts; i++)
+ for (i = 0; i < content_info->hunks->nelts; i++)
{
const hunk_info_t *hi;
+ svn_linenum_t length;
+
+ hi = APR_ARRAY_IDX(content_info->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);
- hi = APR_ARRAY_IDX(target->hunks, i, const hunk_info_t *);
taken = (! hi->rejected &&
- target->current_line >= hi->matched_line &&
- target->current_line < hi->matched_line +
- hi->hunk->original_length);
+ content_info->current_line >= hi->matched_line &&
+ content_info->current_line < (hi->matched_line + length));
if (taken)
break;
}
if (! taken)
{
- *matched_line = target->current_line;
+ *matched_line = content_info->current_line;
if (match_first)
break;
}
}
- if (! target->eof)
- SVN_ERR(seek_to_line(target, target->current_line + 1, iterpool));
+ if (! content_info->eof)
+ SVN_ERR(seek_to_line(content_info, content_info->current_line + 1,
+ iterpool));
}
svn_pool_destroy(iterpool);
return SVN_NO_ERROR;
}
-/* Determine the line at which a HUNK applies to the TARGET file,
- * and return an appropriate hunk_info object in *HI, allocated from
+/* Indicate in *MATCH whether the STREAM matches the modified text of HUNK.
+ * Use CONTENT_INFO for retrieving information on eol and keywords that is
+ * needed for the comparison.
+ * Use SCRATCH_POOL for temporary allocations. */
+static svn_error_t *
+match_existing_target(svn_boolean_t *match,
+ target_content_info_t *content_info,
+ const svn_hunk_t *hunk,
+ svn_stream_t *stream,
+ apr_pool_t *scratch_pool)
+{
+ svn_boolean_t lines_matched;
+ apr_pool_t *iterpool;
+ svn_boolean_t eof;
+ svn_boolean_t hunk_eof;
+
+ SVN_ERR(svn_diff_hunk_reset_modified_text(hunk));
+
+ iterpool = svn_pool_create(scratch_pool);
+ do
+ {
+ svn_stringbuf_t *line;
+ svn_stringbuf_t *hunk_line;
+ const char *line_translated;
+ const char *hunk_line_translated;
+
+ svn_pool_clear(iterpool);
+
+ SVN_ERR(svn_stream_readline_detect_eol(stream, &line, NULL,
+ &eof, 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->data, &line_translated,
+ NULL, FALSE,
+ content_info->keywords,
+ FALSE, iterpool));
+ SVN_ERR(svn_subst_translate_cstring2(hunk_line->data,
+ &hunk_line_translated,
+ NULL, FALSE,
+ content_info->keywords,
+ FALSE, iterpool));
+ lines_matched = ! strcmp(line_translated, hunk_line_translated);
+ if (eof != hunk_eof)
+ {
+ svn_pool_destroy(iterpool);
+ *match = FALSE;
+ return SVN_NO_ERROR;
+ }
+ }
+ while (lines_matched && ! eof && ! hunk_eof);
+ svn_pool_destroy(iterpool);
+
+ *match = (lines_matched && eof == hunk_eof);
+ return SVN_NO_ERROR;
+}
+
+/* Determine the line at which a HUNK applies to the CONTENT_INFO of 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. When this function returns, neither TARGET->CURRENT_LINE nor
+ * matching. When this function returns, neither CONTENT_INFO->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_info_t *content_info,
const svn_hunk_t *hunk, int 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. */
- if (hunk->original_start == 0)
+ * is rejected, unless the file is versioned and its content matches
+ * the file the patch wants to create. */
+ if (original_start == 0 && ! is_prop_hunk)
{
if (target->kind_on_disk == svn_node_file)
- matched_line = 0;
+ {
+ if (target->db_kind == svn_node_file)
+ {
+ svn_boolean_t file_matches;
+ apr_file_t *file;
+ svn_stream_t *stream;
+
+ /* ### dannas: Why not use content_info-stream here? */
+ SVN_ERR(svn_io_file_open(&file, target->local_abspath,
+ APR_READ | APR_BINARY, APR_OS_DEFAULT,
+ scratch_pool));
+ stream = svn_stream_from_aprfile2(file, FALSE, scratch_pool);
+
+ SVN_ERR(match_existing_target(&file_matches, content_info, hunk,
+ stream, scratch_pool));
+ svn_stream_close(stream);
+
+ if (file_matches)
+ {
+ matched_line = 1;
+ already_applied = TRUE;
+ }
+ else
+ matched_line = 0; /* reject */
+ }
+ else
+ matched_line = 0; /* reject */
+ }
else
matched_line = 1;
}
- else if (hunk->original_start > 0 && target->kind_on_disk == svn_node_file)
+ /* 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)
{
- svn_linenum_t saved_line = target->current_line;
+ if (content_info->stream)
+ {
+ svn_boolean_t prop_matches;
+
+ SVN_ERR(match_existing_target(&prop_matches, content_info, hunk,
+ content_info->stream, scratch_pool));
- /* Scan for a match at the line where the hunk thinks it
- * should be going. */
- SVN_ERR(seek_to_line(target, hunk->original_start, scratch_pool));
- if (target->current_line != hunk->original_start)
+ if (prop_matches)
+ {
+ matched_line = 1;
+ already_applied = TRUE;
+ }
+ else
+ matched_line = 0; /* reject */
+ }
+ else
+ matched_line = 1;
+ }
+ /* ### We previously checked for target->kind_on_disk == svn_node_file
+ * ### here. But that wasn't generic enough to cope with properties. How
+ * ### better describe that content_info->stream is only set if we have an
+ * ### existing target? */
+ else if (original_start > 0 && content_info->stream)
+ {
+ svn_linenum_t saved_line = content_info->current_line;
+ svn_linenum_t modified_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)
{
- /* Seek failed. */
- matched_line = 0;
+ 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_info, modified_start,
+ scratch_pool));
+ SVN_ERR(scan_for_match(&matched_line, content_info,
+ hunk, TRUE,
+ modified_start + 1,
+ fuzz, ignore_whitespace, TRUE,
+ cancel_func, cancel_baton,
+ scratch_pool));
+ already_applied = (matched_line == modified_start);
+ }
}
else
- SVN_ERR(scan_for_match(&matched_line, target, hunk, TRUE,
- hunk->original_start + 1, fuzz,
- ignore_whitespace,
- cancel_func, cancel_baton,
- scratch_pool));
+ already_applied = FALSE;
- if (matched_line != hunk->original_start)
+ if (! already_applied)
{
- /* Scan the whole file again from the start. */
- SVN_ERR(seek_to_line(target, 1, scratch_pool));
+ /* Scan for a match at the line where the hunk thinks it
+ * should be going. */
+ SVN_ERR(seek_to_line(content_info, original_start, scratch_pool));
+ if (content_info->current_line != original_start)
+ {
+ /* Seek failed. */
+ matched_line = 0;
+ }
+ else
+ SVN_ERR(scan_for_match(&matched_line, content_info, hunk, TRUE,
+ original_start + 1, fuzz,
+ ignore_whitespace, FALSE,
+ cancel_func, cancel_baton,
+ scratch_pool));
+
+ if (matched_line != original_start)
+ {
+ /* Scan the whole file again from the start. */
+ SVN_ERR(seek_to_line(content_info, 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, target, hunk, FALSE,
- hunk->original_start, fuzz,
- ignore_whitespace,
- 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, target, hunk, TRUE, 0,
- fuzz, ignore_whitespace,
+ /* Scan forward towards the hunk's line and look for a line
+ * where the hunk matches. */
+ SVN_ERR(scan_for_match(&matched_line, content_info, 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_info, hunk,
+ TRUE, 0, fuzz, ignore_whitespace,
+ FALSE, cancel_func, cancel_baton,
+ scratch_pool));
+ }
}
}
- SVN_ERR(seek_to_line(target, saved_line, scratch_pool));
+ SVN_ERR(seek_to_line(content_info, saved_line, scratch_pool));
}
else
{
@@ -821,6 +1234,7 @@ get_hunk_info(hunk_info_t **hi, patch_ta
(*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;
@@ -850,23 +1264,25 @@ try_stream_write(svn_stream_t *stream, c
* 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(patch_target_t *target, svn_linenum_t line,
- apr_pool_t *pool)
+copy_lines_to_target(target_content_info_t *content_info, svn_linenum_t line,
+ const char *patched_path, apr_pool_t *pool)
{
apr_pool_t *iterpool;
iterpool = svn_pool_create(pool);
- while ((target->current_line < line || line == 0) && ! target->eof)
+ while ((content_info->current_line < line || line == 0)
+ && ! content_info->eof)
{
const char *target_line;
svn_pool_clear(iterpool);
- SVN_ERR(read_line(target, &target_line, iterpool, iterpool));
- if (! target->eof)
- target_line = apr_pstrcat(iterpool, target_line, target->eol_str, NULL);
+ SVN_ERR(read_line(content_info, &target_line, iterpool, iterpool));
+ if (! content_info->eof)
+ target_line = apr_pstrcat(iterpool, target_line, content_info->eol_str,
+ NULL);
- SVN_ERR(try_stream_write(target->patched, target->patched_path,
+ SVN_ERR(try_stream_write(content_info->patched, patched_path,
target_line, strlen(target_line), iterpool));
}
svn_pool_destroy(iterpool);
@@ -875,24 +1291,44 @@ copy_lines_to_target(patch_target_t *tar
}
/* Write the diff text of the hunk described by HI to the
- * reject stream of TARGET, and mark TARGET as having had rejects.
+ * reject stream of CONTENT_INFO, and mark TARGET as having had rejects.
* Do temporary allocations in POOL. */
static svn_error_t *
-reject_hunk(patch_target_t *target, hunk_info_t *hi, apr_pool_t *pool)
+reject_hunk(patch_target_t *target, target_content_info_t *content_info,
+ hunk_info_t *hi, const char *prop_name, apr_pool_t *pool)
{
const char *hunk_header;
apr_size_t len;
svn_boolean_t eof;
apr_pool_t *iterpool;
- hunk_header = apr_psprintf(pool, "@@ -%lu,%lu +%lu,%lu @@%s",
- hi->hunk->original_start,
- hi->hunk->original_length,
- hi->hunk->modified_start,
- hi->hunk->modified_length,
- APR_EOL_STR);
+ if (prop_name)
+ {
+ const char *prop_header;
+
+ /* ### Print 'Added', 'Deleted' or 'Modified' instead of 'Propperty'.
+ */
+ prop_header = apr_psprintf(pool, "Property: %s\n", prop_name);
+ len = strlen(prop_header);
+
+ SVN_ERR(svn_stream_write(content_info->reject, prop_header, &len));
+
+ hunk_header = apr_psprintf(pool, "## -%lu,%lu +%lu,%lu ##%s",
+ svn_diff_hunk_get_original_start(hi->hunk),
+ svn_diff_hunk_get_original_length(hi->hunk),
+ svn_diff_hunk_get_modified_start(hi->hunk),
+ svn_diff_hunk_get_modified_length(hi->hunk),
+ APR_EOL_STR);
+ }
+ else
+ hunk_header = apr_psprintf(pool, "@@ -%lu,%lu +%lu,%lu @@%s",
+ svn_diff_hunk_get_original_start(hi->hunk),
+ svn_diff_hunk_get_original_length(hi->hunk),
+ svn_diff_hunk_get_modified_start(hi->hunk),
+ svn_diff_hunk_get_modified_length(hi->hunk),
+ APR_EOL_STR);
len = strlen(hunk_header);
- SVN_ERR(svn_stream_write(target->reject, hunk_header, &len));
+ SVN_ERR(svn_stream_write(content_info->reject, hunk_header, &len));
iterpool = svn_pool_create(pool);
do
@@ -902,37 +1338,43 @@ reject_hunk(patch_target_t *target, hunk
svn_pool_clear(iterpool);
- SVN_ERR(svn_stream_readline_detect_eol(hi->hunk->diff_text, &hunk_line,
- &eol_str, &eof, iterpool));
+ SVN_ERR(svn_diff_hunk_readline_diff_text(hi->hunk, &hunk_line, &eol_str,
+ &eof, iterpool, iterpool));
if (! eof)
{
if (hunk_line->len >= 1)
- SVN_ERR(try_stream_write(target->reject, target->reject_path,
+ SVN_ERR(try_stream_write(content_info->reject, target->reject_path,
hunk_line->data, hunk_line->len,
iterpool));
if (eol_str)
- SVN_ERR(try_stream_write(target->reject, target->reject_path,
+ SVN_ERR(try_stream_write(content_info->reject, target->reject_path,
eol_str, strlen(eol_str), iterpool));
}
}
while (! eof);
svn_pool_destroy(iterpool);
- target->had_rejects = TRUE;
+ if (prop_name)
+ target->had_prop_rejects = TRUE;
+ else
+ target->had_rejects = TRUE;
return SVN_NO_ERROR;
}
/* Write the modified text of hunk described by HI to the patched
- * stream of TARGET. Do temporary allocations in POOL. */
+ * stream of CONTENT_INFO. Do temporary allocations in POOL. */
static svn_error_t *
-apply_hunk(patch_target_t *target, hunk_info_t *hi, apr_pool_t *pool)
+apply_hunk(patch_target_t *target, target_content_info_t *content_info,
+ 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;
- if (target->kind_on_disk == svn_node_file)
+ /* ### 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;
@@ -940,18 +1382,19 @@ apply_hunk(patch_target_t *target, hunk_
* 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(target, hi->matched_line + hi->fuzz,
- pool));
+ SVN_ERR(copy_lines_to_target(content_info, hi->matched_line + hi->fuzz,
+ target->patched_path, pool));
/* Skip the target's version of the hunk.
* Don't skip trailing lines which matched with fuzz. */
- line = target->current_line + hi->hunk->original_length - (2 * hi->fuzz);
- SVN_ERR(seek_to_line(target, line, pool));
- if (target->current_line != line && ! target->eof)
+ line = content_info->current_line +
+ svn_diff_hunk_get_original_length(hi->hunk) - (2 * hi->fuzz);
+ SVN_ERR(seek_to_line(content_info, line, pool));
+ if (content_info->current_line != line && ! content_info->eof)
{
/* Seek failed, reject this hunk. */
hi->rejected = TRUE;
- SVN_ERR(reject_hunk(target, hi, pool));
+ SVN_ERR(reject_hunk(target, content_info, hi, prop_name, pool));
return SVN_NO_ERROR;
}
}
@@ -959,6 +1402,7 @@ apply_hunk(patch_target_t *target, hunk_
/* Write the hunk's version to the patched result.
* Don't write the lines which matched with fuzz. */
lines_read = 0;
+ SVN_ERR(svn_diff_hunk_reset_modified_text(hi->hunk));
iterpool = svn_pool_create(pool);
do
{
@@ -967,32 +1411,80 @@ apply_hunk(patch_target_t *target, hunk_
svn_pool_clear(iterpool);
- SVN_ERR(svn_stream_readline_detect_eol(hi->hunk->modified_text,
- &hunk_line, &eol_str,
- &eof, iterpool));
+ SVN_ERR(svn_diff_hunk_readline_modified_text(hi->hunk, &hunk_line,
+ &eol_str, &eof,
+ iterpool, iterpool));
lines_read++;
if (! eof && lines_read > hi->fuzz &&
- lines_read <= hi->hunk->modified_length - hi->fuzz)
+ lines_read <= svn_diff_hunk_get_modified_length(hi->hunk) - hi->fuzz)
{
if (hunk_line->len >= 1)
- SVN_ERR(try_stream_write(target->patched, target->patched_path,
+ SVN_ERR(try_stream_write(content_info->patched,
+ target->patched_path,
hunk_line->data, hunk_line->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 (target->eol_style != svn_subst_eol_style_none)
- eol_str = target->eol_str;
+ if (content_info->eol_style != svn_subst_eol_style_none)
+ eol_str = content_info->eol_str;
- SVN_ERR(try_stream_write(target->patched, target->patched_path,
- eol_str, strlen(eol_str), iterpool));
+ SVN_ERR(try_stream_write(content_info->patched,
+ target->patched_path, eol_str,
+ strlen(eol_str), 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 is set, 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;
}
@@ -1039,8 +1531,13 @@ send_patch_notification(const patch_targ
notify->content_state = svn_wc_notify_state_conflicted;
else if (target->local_mods)
notify->content_state = svn_wc_notify_state_merged;
- else
+ 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);
@@ -1049,33 +1546,44 @@ send_patch_notification(const patch_targ
{
int i;
apr_pool_t *iterpool;
+ apr_hash_index_t *hash_index;
iterpool = svn_pool_create(pool);
- for (i = 0; i < target->hunks->nelts; i++)
+ for (i = 0; i < target->content_info->hunks->nelts; i++)
{
- hunk_info_t *hi;
+ const hunk_info_t *hi;
svn_pool_clear(iterpool);
- hi = APR_ARRAY_IDX(target->hunks, i, hunk_info_t *);
+ hi = APR_ARRAY_IDX(target->content_info->hunks, i, hunk_info_t *);
- if (hi->rejected)
- action = svn_wc_notify_patch_rejected_hunk;
- else
- action = svn_wc_notify_patch_applied_hunk;
+ 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);
- notify = svn_wc_create_notify(target->local_abspath
- ? target->local_abspath
- : target->local_relpath,
- action, pool);
- notify->hunk_original_start = hi->hunk->original_start;
- notify->hunk_original_length = hi->hunk->original_length;
- notify->hunk_modified_start = hi->hunk->modified_start;
- notify->hunk_modified_length = hi->hunk->modified_length;
- notify->hunk_matched_line = hi->matched_line;
- notify->hunk_fuzz = hi->fuzz;
+ for (i = 0; i < prop_target->content_info->hunks->nelts; i++)
+ {
+ const hunk_info_t *hi;
+
+ svn_pool_clear(iterpool);
- (*ctx->notify_func2)(ctx->notify_baton2, notify, pool);
+ hi = APR_ARRAY_IDX(prop_target->content_info->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);
}
@@ -1110,6 +1618,7 @@ apply_one_patch(patch_target_t **patch_t
apr_pool_t *iterpool;
int i;
static const int 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));
@@ -1151,37 +1660,43 @@ apply_one_patch(patch_target_t **patch_t
* If no match is found initially, try with fuzz. */
do
{
- SVN_ERR(get_hunk_info(&hi, target, hunk, fuzz,
+ SVN_ERR(get_hunk_info(&hi, target, target->content_info, hunk, fuzz,
ignore_whitespace,
+ FALSE /* is_prop_hunk */,
cancel_func, cancel_baton,
result_pool, iterpool));
fuzz++;
}
- while (hi->rejected && fuzz <= MAX_FUZZ);
+ while (hi->rejected && fuzz <= MAX_FUZZ && ! hi->already_applied);
- APR_ARRAY_PUSH(target->hunks, hunk_info_t *) = hi;
+ APR_ARRAY_PUSH(target->content_info->hunks, hunk_info_t *) = hi;
}
/* Apply or reject hunks. */
- for (i = 0; i < target->hunks->nelts; i++)
+ for (i = 0; i < target->content_info->hunks->nelts; i++)
{
hunk_info_t *hi;
svn_pool_clear(iterpool);
- hi = APR_ARRAY_IDX(target->hunks, i, hunk_info_t *);
- if (hi->rejected)
- SVN_ERR(reject_hunk(target, hi, iterpool));
+ hi = APR_ARRAY_IDX(target->content_info->hunks, i, hunk_info_t *);
+ if (hi->already_applied)
+ continue;
+ else if (hi->rejected)
+ SVN_ERR(reject_hunk(target, target->content_info, hi,
+ NULL /* prop_name */,
+ iterpool));
else
- SVN_ERR(apply_hunk(target, hi, iterpool));
+ SVN_ERR(apply_hunk(target, target->content_info, hi,
+ NULL /* prop_name */, iterpool));
}
- svn_pool_destroy(iterpool);
if (target->kind_on_disk == svn_node_file)
{
/* Copy any remaining lines to target. */
- SVN_ERR(copy_lines_to_target(target, 0, scratch_pool));
- if (! target->eof)
+ SVN_ERR(copy_lines_to_target(target->content_info, 0,
+ target->patched_path, scratch_pool));
+ if (! target->content_info->eof)
{
/* We could not copy the entire target file to the temporary file,
* and would truncate the target if we copied the temporary file
@@ -1190,12 +1705,142 @@ apply_one_patch(patch_target_t **patch_t
}
}
- /* Close the streams of the target so that their content is flushed
- * to disk. This will also close underlying streams and files. */
- if (target->kind_on_disk == svn_node_file)
- SVN_ERR(svn_stream_close(target->stream));
- SVN_ERR(svn_stream_close(target->patched));
- SVN_ERR(svn_stream_close(target->reject));
+ /* Match property hunks. */
+ for (hash_index = apr_hash_first(result_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;
+ target_content_info_t *prop_content_info;
+
+ /* Fetching the parsed info for one property */
+ prop_name = svn__apr_hash_index_key(hash_index);
+ prop_patch = svn__apr_hash_index_val(hash_index);
+
+ /* Fetch the prop_content_info we'll use to store the matched hunks
+ * in. */
+ prop_target = apr_hash_get(target->prop_targets, prop_name,
+ APR_HASH_KEY_STRING);
+ prop_content_info = prop_target->content_info;
+
+ for (i = 0; i < prop_patch->hunks->nelts; i++)
+ {
+ svn_hunk_t *hunk;
+ hunk_info_t *hi;
+ int fuzz = 0;
+
+ svn_pool_clear(iterpool);
+
+ if (cancel_func)
+ SVN_ERR((cancel_func)(cancel_baton));
+
+ hunk = APR_ARRAY_IDX(prop_patch->hunks, i, svn_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_content_info, 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_content_info->hunks, hunk_info_t *) = hi;
+ }
+ }
+
+ /* Apply or reject property hunks. */
+ for (hash_index = apr_hash_first(result_pool, target->prop_targets);
+ hash_index;
+ hash_index = apr_hash_next(hash_index))
+ {
+ prop_patch_target_t *prop_target;
+ target_content_info_t *prop_content_info;
+ const char *prop_patched_path;
+
+ prop_target = svn__apr_hash_index_val(hash_index);
+ prop_content_info = prop_target->content_info;
+ prop_patched_path = prop_target->patched_path;
+
+ for (i = 0; i < prop_content_info->hunks->nelts; i++)
+ {
+ hunk_info_t *hi;
+
+ svn_pool_clear(iterpool);
+
+ hi = APR_ARRAY_IDX(prop_content_info->hunks, i, hunk_info_t *);
+ if (hi->already_applied)
+ continue;
+ else if (hi->rejected)
+ SVN_ERR(reject_hunk(target, prop_content_info, hi,
+ prop_target->name,
+ iterpool));
+ else
+ SVN_ERR(apply_hunk(target, prop_content_info, hi,
+ prop_target->name,
+ iterpool));
+ }
+
+ if (prop_content_info->stream)
+ {
+ /* Copy any remaining lines to target. */
+ SVN_ERR(copy_lines_to_target(prop_content_info, 0,
+ prop_patched_path, scratch_pool));
+ if (! prop_content_info->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.
+ *
+ * ### dannas: Do we really want to skip an entire target
+ * ### if one of the properties does not apply cleanly,
+ * ### e.g. both text changes and all prop changes will not be
+ * ### installed. */
+ target->skipped = TRUE;
+ }
+ }
+ }
+
+ svn_pool_destroy(iterpool);
+
+ {
+ apr_hash_index_t *hi;
+ target_content_info_t *prop_content_info;
+
+ /* Close the streams of the target so that their content is flushed
+ * to disk. This will also close underlying streams and files.
+ * First the streams belonging to properties .. */
+ for (hi = apr_hash_first(result_pool, target->prop_targets);
+ hi;
+ hi = apr_hash_next(hi))
+ {
+ prop_patch_target_t *prop_target;
+ prop_target = svn__apr_hash_index_val(hi);
+ prop_content_info = prop_target->content_info;
+
+ /* ### If the prop did not exist pre-patching we'll not have a
+ * ### stream to read from. Find a better way to store info on
+ * ### the existence of the target prop. */
+ if (prop_content_info->stream)
+ svn_stream_close(prop_content_info->stream);
+
+ svn_stream_close(prop_content_info->patched);
+ }
+
+
+ /* .. And then streams associted with the file. The reject stream is
+ * shared between all target_content_info structures. */
+ if (target->kind_on_disk == svn_node_file)
+ SVN_ERR(svn_stream_close(target->content_info->stream));
+ SVN_ERR(svn_stream_close(target->content_info->patched));
+ SVN_ERR(svn_stream_close(target->content_info->reject));
+ }
if (! target->skipped)
{
@@ -1266,14 +1911,15 @@ create_missing_parents(patch_target_t *t
apr_pool_t *iterpool;
/* Check if we can safely create the target's parent. */
- local_abspath = apr_pstrdup(scratch_pool, abs_wc_path);
+ 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 kind;
+ svn_node_kind_t wc_kind, disk_kind;
+ svn_boolean_t is_deleted;
svn_pool_clear(iterpool);
@@ -1281,49 +1927,48 @@ create_missing_parents(patch_target_t *t
const char *);
local_abspath = svn_dirent_join(local_abspath, component, scratch_pool);
- SVN_ERR(svn_wc_read_kind(&kind, ctx->wc_ctx, local_abspath, TRUE,
+ SVN_ERR(svn_wc_read_kind(&wc_kind, ctx->wc_ctx, local_abspath, TRUE,
iterpool));
- if (kind == svn_node_file)
+
+ SVN_ERR(svn_io_check_path(local_abspath, &disk_kind, iterpool));
+
+ if (wc_kind != svn_node_none)
+ SVN_ERR(svn_wc__node_is_status_deleted(&is_deleted,
+ ctx->wc_ctx,
+ local_abspath,
+ iterpool));
+ else
+ is_deleted = FALSE;
+
+ if (disk_kind == svn_node_file
+ || (wc_kind == svn_node_file && !is_deleted))
{
- /* Obstructed. */
+ /* on-disk files and missing files are obstructions */
target->skipped = TRUE;
break;
}
- else if (kind == svn_node_dir)
+ else if (wc_kind == svn_node_dir)
{
- /* ### wc-ng should eventually be able to replace
- * directories in-place, so this schedule conflict
- * check will go away. We could then also make the
- * svn_wc_read_kind() call above ignore hidden
- * nodes.*/
- svn_boolean_t is_deleted;
-
- SVN_ERR(svn_wc__node_is_status_deleted(&is_deleted,
- ctx->wc_ctx,
- local_abspath,
- iterpool));
if (is_deleted)
{
target->skipped = TRUE;
break;
}
+ /* continue one level deeper */
present_components++;
}
+ else if (disk_kind == svn_node_dir)
+ {
+ /* Obstructed. ### BH: why? We can just add a directory */
+ target->skipped = TRUE;
+ break;
+ }
else
{
- /* The WC_DB doesn't know much about this node.
- * Check what's on disk. */
- svn_node_kind_t disk_kind;
-
- SVN_ERR(svn_io_check_path(local_abspath, &disk_kind,
- iterpool));
- if (disk_kind != svn_node_none)
- {
- /* An unversioned item is in the way. */
- target->skipped = TRUE;
- break;
- }
+ /* It's not a file, it's not a dir..
+ Let's add a dir */
+ break;
}
}
@@ -1339,6 +1984,15 @@ create_missing_parents(patch_target_t *t
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;
@@ -1369,8 +2023,6 @@ create_missing_parents(patch_target_t *t
* to version control. Allow cancellation since we
* have not modified the working copy yet for this
* target. */
- SVN_ERR(svn_io_dir_make(local_abspath, APR_OS_DEFAULT,
- iterpool));
SVN_ERR(svn_wc_add4(ctx->wc_ctx, local_abspath,
svn_depth_infinity,
NULL, SVN_INVALID_REVNUM,
@@ -1423,7 +2075,25 @@ install_patched_target(patch_target_t *t
svn_dirent_dirname(target->local_abspath,
pool),
FALSE, pool));
- if (parent_db_kind != svn_node_dir)
+
+ /* We don't allow targets to be added under dirs scheduled for
+ * deletion. */
+ if (parent_db_kind == svn_node_dir)
+ {
+ const char *parent_abspath;
+ svn_boolean_t is_deleted;
+
+ parent_abspath = svn_dirent_dirname(target->local_abspath,
+ pool);
+ SVN_ERR(svn_wc__node_is_status_deleted(&is_deleted, ctx->wc_ctx,
+ parent_abspath, pool));
+ if (is_deleted)
+ {
+ target->skipped = TRUE;
+ return SVN_NO_ERROR;
+ }
+ }
+ else
SVN_ERR(create_missing_parents(target, abs_wc_path, ctx,
dry_run, pool));
}
@@ -1453,15 +2123,111 @@ install_patched_target(patch_target_t *t
}
}
- /* Write out rejected hunks, if any. */
- if (! dry_run && ! target->skipped && target->had_rejects)
+ 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)
+{
+ 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),
+ 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 tempoary 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;
+
+ if (dry_run)
+ return SVN_NO_ERROR;
+
+ 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);
+ apr_file_t *file;
+ svn_stream_t *patched_stream;
+ svn_stringbuf_t *line;
+ svn_stringbuf_t *prop_content;
+ const char *eol_str;
+ svn_boolean_t eof;
+
+ svn_pool_clear(iterpool);
+
+ /* For a deleted prop we only set the value to NULL. */
+ if (prop_target->operation == svn_diff_op_deleted)
+ {
+ SVN_ERR(svn_wc_prop_set4(ctx->wc_ctx, target->local_abspath,
+ prop_target->name, NULL,
+ TRUE /* skip_checks */,
+ NULL, NULL,
+ iterpool));
+ continue;
+ }
+
+ /* A property is usually one line long.
+ * ### Is this the optimal size to allocate? */
+ prop_content = svn_stringbuf_create_ensure(80, scratch_pool);
+
+ /* svn_wc_prop_set4() wants a svn_string_t for input so we need to
+ * open the tmp file for reading again. */
+ SVN_ERR(svn_io_file_open(&file, prop_target->patched_path,
+ APR_READ | APR_BINARY, APR_OS_DEFAULT,
+ scratch_pool));
+
+ patched_stream = svn_stream_from_aprfile2(file, FALSE /* disown */,
+ iterpool);
+ do
+ {
+ SVN_ERR(svn_stream_readline_detect_eol(patched_stream,
+ &line, &eol_str,
+ &eof,
+ iterpool));
+
+ svn_stringbuf_appendstr(prop_content, line);
+
+ if (eol_str)
+ svn_stringbuf_appendcstr(prop_content, eol_str);
+ }
+ while (! eof);
+
+ svn_stream_close(patched_stream);
+
+ /* ### How should we handle SVN_ERR_ILLEGAL_TARGET and
+ * ### SVN_ERR_BAD_MIME_TYPE? */
+ SVN_ERR(svn_wc_prop_set4(ctx->wc_ctx, target->local_abspath,
+ prop_target->name,
+ svn_string_create_from_buf(prop_content,
+ iterpool),
+ TRUE /* skip_checks */,
+ NULL, NULL,
+ iterpool));
+ }
+
+ svn_pool_destroy(iterpool);
return SVN_NO_ERROR;
}
@@ -1828,9 +2594,20 @@ apply_patches(void *baton,
patch_target_info_t *) = target_info;
if (! target->skipped)
- SVN_ERR(install_patched_target(target, btn->abs_wc_path,
- btn->ctx, btn->dry_run,
- iterpool));
+ {
+ if (target->has_text_changes)
+ SVN_ERR(install_patched_target(target, btn->abs_wc_path,
+ btn->ctx, btn->dry_run,
+ iterpool));
+
+ if (target->has_prop_changes)
+ SVN_ERR(install_patched_prop_targets(target, btn->ctx,
+ btn->dry_run,
+ iterpool));
+
+ SVN_ERR(write_out_rejected_hunks(target, btn->dry_run,
+ iterpool));
+ }
SVN_ERR(send_patch_notification(target, btn->ctx, iterpool));
}
@@ -1880,7 +2657,8 @@ svn_client_patch(const char *patch_abspa
baton.patch_func = patch_func;
baton.patch_baton = patch_baton;
- return svn_error_return(svn_wc__call_with_write_lock(apply_patches, &baton,
- ctx->wc_ctx, local_abspath,
- result_pool, scratch_pool));
+ return svn_error_return(
+ svn_wc__call_with_write_lock(apply_patches, &baton,
+ ctx->wc_ctx, local_abspath, FALSE,
+ result_pool, scratch_pool));
}