summaryrefslogtreecommitdiffstats
path: root/subversion/libsvn_wc/props.c
diff options
context:
space:
mode:
Diffstat (limited to 'subversion/libsvn_wc/props.c')
-rw-r--r--subversion/libsvn_wc/props.c2344
1 files changed, 2344 insertions, 0 deletions
diff --git a/subversion/libsvn_wc/props.c b/subversion/libsvn_wc/props.c
new file mode 100644
index 0000000..a7b2339
--- /dev/null
+++ b/subversion/libsvn_wc/props.c
@@ -0,0 +1,2344 @@
+/*
+ * props.c : routines dealing with properties in the working copy
+ *
+ * ====================================================================
+ * 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.
+ * ====================================================================
+ */
+
+
+
+#include <stdlib.h>
+#include <string.h>
+
+#include <apr_pools.h>
+#include <apr_hash.h>
+#include <apr_tables.h>
+#include <apr_file_io.h>
+#include <apr_strings.h>
+#include <apr_general.h>
+
+#include "svn_types.h"
+#include "svn_string.h"
+#include "svn_pools.h"
+#include "svn_dirent_uri.h"
+#include "svn_path.h"
+#include "svn_error.h"
+#include "svn_props.h"
+#include "svn_io.h"
+#include "svn_hash.h"
+#include "svn_mergeinfo.h"
+#include "svn_wc.h"
+#include "svn_utf.h"
+#include "svn_diff.h"
+#include "svn_sorts.h"
+
+#include "private/svn_wc_private.h"
+#include "private/svn_mergeinfo_private.h"
+#include "private/svn_skel.h"
+#include "private/svn_string_private.h"
+#include "private/svn_subr_private.h"
+
+#include "wc.h"
+#include "props.h"
+#include "translate.h"
+#include "workqueue.h"
+#include "conflicts.h"
+
+#include "svn_private_config.h"
+
+/* Forward declaration. */
+static svn_error_t *
+prop_conflict_from_skel(const svn_string_t **conflict_desc,
+ const svn_skel_t *skel,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool);
+
+/* Given a *SINGLE* property conflict in PROP_SKEL, generate a description
+ for it, and write it to STREAM, along with a trailing EOL sequence.
+
+ See prop_conflict_from_skel() for details on PROP_SKEL. */
+static svn_error_t *
+append_prop_conflict(svn_stream_t *stream,
+ const svn_skel_t *prop_skel,
+ apr_pool_t *pool)
+{
+ /* TODO: someday, perhaps prefix each conflict_description with a
+ timestamp or something? */
+ const svn_string_t *conflict_desc;
+
+ SVN_ERR(prop_conflict_from_skel(&conflict_desc, prop_skel, pool, pool));
+
+ return svn_stream_puts(stream, conflict_desc->data);
+}
+
+/*---------------------------------------------------------------------*/
+
+/*** Merging propchanges into the working copy ***/
+
+
+/* Parse FROM_PROP_VAL and TO_PROP_VAL into mergeinfo hashes, and
+ calculate the deltas between them. */
+static svn_error_t *
+diff_mergeinfo_props(svn_mergeinfo_t *deleted, svn_mergeinfo_t *added,
+ const svn_string_t *from_prop_val,
+ const svn_string_t *to_prop_val, apr_pool_t *pool)
+{
+ if (svn_string_compare(from_prop_val, to_prop_val))
+ {
+ /* Don't bothering parsing identical mergeinfo. */
+ *deleted = apr_hash_make(pool);
+ *added = apr_hash_make(pool);
+ }
+ else
+ {
+ svn_mergeinfo_t from, to;
+ SVN_ERR(svn_mergeinfo_parse(&from, from_prop_val->data, pool));
+ SVN_ERR(svn_mergeinfo_parse(&to, to_prop_val->data, pool));
+ SVN_ERR(svn_mergeinfo_diff2(deleted, added, from, to,
+ TRUE, pool, pool));
+ }
+ return SVN_NO_ERROR;
+}
+
+/* Parse the mergeinfo from PROP_VAL1 and PROP_VAL2, combine it, then
+ reconstitute it into *OUTPUT. Call when the WC's mergeinfo has
+ been modified to combine it with incoming mergeinfo from the
+ repos. */
+static svn_error_t *
+combine_mergeinfo_props(const svn_string_t **output,
+ const svn_string_t *prop_val1,
+ const svn_string_t *prop_val2,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ svn_mergeinfo_t mergeinfo1, mergeinfo2;
+ svn_string_t *mergeinfo_string;
+
+ SVN_ERR(svn_mergeinfo_parse(&mergeinfo1, prop_val1->data, scratch_pool));
+ SVN_ERR(svn_mergeinfo_parse(&mergeinfo2, prop_val2->data, scratch_pool));
+ SVN_ERR(svn_mergeinfo_merge2(mergeinfo1, mergeinfo2, scratch_pool,
+ scratch_pool));
+ SVN_ERR(svn_mergeinfo_to_string(&mergeinfo_string, mergeinfo1, result_pool));
+ *output = mergeinfo_string;
+ return SVN_NO_ERROR;
+}
+
+/* Perform a 3-way merge operation on mergeinfo. FROM_PROP_VAL is
+ the "base" property value, WORKING_PROP_VAL is the current value,
+ and TO_PROP_VAL is the new value. */
+static svn_error_t *
+combine_forked_mergeinfo_props(const svn_string_t **output,
+ const svn_string_t *from_prop_val,
+ const svn_string_t *working_prop_val,
+ const svn_string_t *to_prop_val,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ svn_mergeinfo_t from_mergeinfo, l_deleted, l_added, r_deleted, r_added;
+ svn_string_t *mergeinfo_string;
+
+ /* ### OPTIMIZE: Use from_mergeinfo when diff'ing. */
+ SVN_ERR(diff_mergeinfo_props(&l_deleted, &l_added, from_prop_val,
+ working_prop_val, scratch_pool));
+ SVN_ERR(diff_mergeinfo_props(&r_deleted, &r_added, from_prop_val,
+ to_prop_val, scratch_pool));
+ SVN_ERR(svn_mergeinfo_merge2(l_deleted, r_deleted,
+ scratch_pool, scratch_pool));
+ SVN_ERR(svn_mergeinfo_merge2(l_added, r_added,
+ scratch_pool, scratch_pool));
+
+ /* Apply the combined deltas to the base. */
+ SVN_ERR(svn_mergeinfo_parse(&from_mergeinfo, from_prop_val->data,
+ scratch_pool));
+ SVN_ERR(svn_mergeinfo_merge2(from_mergeinfo, l_added,
+ scratch_pool, scratch_pool));
+
+ SVN_ERR(svn_mergeinfo_remove2(&from_mergeinfo, l_deleted, from_mergeinfo,
+ TRUE, scratch_pool, scratch_pool));
+
+ SVN_ERR(svn_mergeinfo_to_string(&mergeinfo_string, from_mergeinfo,
+ result_pool));
+ *output = mergeinfo_string;
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_wc_merge_props3(svn_wc_notify_state_t *state,
+ svn_wc_context_t *wc_ctx,
+ const char *local_abspath,
+ const svn_wc_conflict_version_t *left_version,
+ const svn_wc_conflict_version_t *right_version,
+ apr_hash_t *baseprops,
+ const apr_array_header_t *propchanges,
+ svn_boolean_t dry_run,
+ svn_wc_conflict_resolver_func2_t conflict_func,
+ void *conflict_baton,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ apr_pool_t *scratch_pool)
+{
+ int i;
+ svn_wc__db_status_t status;
+ svn_node_kind_t kind;
+ apr_hash_t *pristine_props = NULL;
+ apr_hash_t *actual_props;
+ apr_hash_t *new_actual_props;
+ svn_boolean_t had_props, props_mod;
+ svn_boolean_t have_base;
+ svn_boolean_t conflicted;
+ svn_skel_t *work_items = NULL;
+ svn_skel_t *conflict_skel = NULL;
+ svn_wc__db_t *db = wc_ctx->db;
+
+ /* IMPORTANT: svn_wc_merge_prop_diffs relies on the fact that baseprops
+ may be NULL. */
+
+ SVN_ERR(svn_wc__db_read_info(&status, &kind, NULL, NULL, NULL, NULL, NULL,
+ NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
+ NULL, NULL, NULL, NULL, NULL, &conflicted, NULL,
+ &had_props, &props_mod, &have_base, NULL, NULL,
+ db, local_abspath,
+ scratch_pool, scratch_pool));
+
+ /* Checks whether the node exists and returns the hidden flag */
+ if (status == svn_wc__db_status_not_present
+ || status == svn_wc__db_status_server_excluded
+ || status == svn_wc__db_status_excluded)
+ {
+ return svn_error_createf(
+ SVN_ERR_WC_PATH_NOT_FOUND, NULL,
+ _("The node '%s' was not found."),
+ svn_dirent_local_style(local_abspath, scratch_pool));
+ }
+ else if (status != svn_wc__db_status_normal
+ && status != svn_wc__db_status_added
+ && status != svn_wc__db_status_incomplete)
+ {
+ return svn_error_createf(
+ SVN_ERR_WC_PATH_UNEXPECTED_STATUS, NULL,
+ _("The node '%s' does not have properties in this state."),
+ svn_dirent_local_style(local_abspath, scratch_pool));
+ }
+ else if (conflicted)
+ {
+ svn_boolean_t text_conflicted;
+ svn_boolean_t prop_conflicted;
+ svn_boolean_t tree_conflicted;
+
+ SVN_ERR(svn_wc__internal_conflicted_p(&text_conflicted,
+ &prop_conflicted,
+ &tree_conflicted,
+ db, local_abspath,
+ scratch_pool));
+
+ /* We can't install two text/prop conflicts on a single node, so
+ avoid even checking that we have to merge it */
+ if (text_conflicted || prop_conflicted || tree_conflicted)
+ {
+ return svn_error_createf(
+ SVN_ERR_WC_PATH_UNEXPECTED_STATUS, NULL,
+ _("Can't merge into conflicted node '%s'"),
+ svn_dirent_local_style(local_abspath,
+ scratch_pool));
+ }
+ /* else: Conflict was resolved by removing markers */
+ }
+
+ /* The PROPCHANGES may not have non-"normal" properties in it. If entry
+ or wc props were allowed, then the following code would install them
+ into the BASE and/or WORKING properties(!). */
+ for (i = propchanges->nelts; i--; )
+ {
+ const svn_prop_t *change = &APR_ARRAY_IDX(propchanges, i, svn_prop_t);
+
+ if (!svn_wc_is_normal_prop(change->name))
+ return svn_error_createf(SVN_ERR_BAD_PROP_KIND, NULL,
+ _("The property '%s' may not be merged "
+ "into '%s'."),
+ change->name,
+ svn_dirent_local_style(local_abspath,
+ scratch_pool));
+ }
+
+ if (had_props)
+ SVN_ERR(svn_wc__db_read_pristine_props(&pristine_props, db, local_abspath,
+ scratch_pool, scratch_pool));
+ if (pristine_props == NULL)
+ pristine_props = apr_hash_make(scratch_pool);
+
+ if (props_mod)
+ SVN_ERR(svn_wc__get_actual_props(&actual_props, db, local_abspath,
+ scratch_pool, scratch_pool));
+ else
+ actual_props = pristine_props;
+
+ /* Note that while this routine does the "real" work, it's only
+ prepping tempfiles and writing log commands. */
+ SVN_ERR(svn_wc__merge_props(&conflict_skel, state,
+ &new_actual_props,
+ db, local_abspath,
+ baseprops /* server_baseprops */,
+ pristine_props,
+ actual_props,
+ propchanges,
+ scratch_pool, scratch_pool));
+
+ if (dry_run)
+ {
+ return SVN_NO_ERROR;
+ }
+
+ {
+ const char *dir_abspath;
+
+ if (kind == svn_node_dir)
+ dir_abspath = local_abspath;
+ else
+ dir_abspath = svn_dirent_dirname(local_abspath, scratch_pool);
+
+ /* Verify that we're holding this directory's write lock. */
+ SVN_ERR(svn_wc__write_check(db, dir_abspath, scratch_pool));
+ }
+
+ if (conflict_skel)
+ {
+ svn_skel_t *work_item;
+ SVN_ERR(svn_wc__conflict_skel_set_op_merge(conflict_skel,
+ left_version,
+ right_version,
+ scratch_pool,
+ scratch_pool));
+
+ SVN_ERR(svn_wc__conflict_create_markers(&work_item,
+ db, local_abspath,
+ conflict_skel,
+ scratch_pool, scratch_pool));
+
+ work_items = svn_wc__wq_merge(work_items, work_item, scratch_pool);
+ }
+
+ /* After a (not-dry-run) merge, we ALWAYS have props to save. */
+ SVN_ERR_ASSERT(new_actual_props != NULL);
+
+ SVN_ERR(svn_wc__db_op_set_props(db, local_abspath, new_actual_props,
+ svn_wc__has_magic_property(propchanges),
+ conflict_skel,
+ work_items,
+ scratch_pool));
+
+ if (work_items != NULL)
+ SVN_ERR(svn_wc__wq_run(db, local_abspath, cancel_func, cancel_baton,
+ scratch_pool));
+
+ /* If there is a conflict, try to resolve it. */
+ if (conflict_skel && conflict_func)
+ {
+ svn_boolean_t prop_conflicted;
+
+ SVN_ERR(svn_wc__conflict_invoke_resolver(db, local_abspath, conflict_skel,
+ NULL /* merge_options */,
+ conflict_func, conflict_baton,
+ cancel_func, cancel_baton,
+ scratch_pool));
+
+ /* Reset *STATE if all prop conflicts were resolved. */
+ SVN_ERR(svn_wc__internal_conflicted_p(
+ NULL, &prop_conflicted, NULL,
+ wc_ctx->db, local_abspath, scratch_pool));
+ if (! prop_conflicted)
+ *state = svn_wc_notify_state_merged;
+ }
+
+ return SVN_NO_ERROR;
+}
+
+
+/* Generate a message to describe the property conflict among these four
+ values.
+
+ Note that this function (currently) interprets the property values as
+ strings, but they could actually be binary values. We'll keep the
+ types as svn_string_t in case we fix this in the future. */
+static svn_stringbuf_t *
+generate_conflict_message(const char *propname,
+ const svn_string_t *original,
+ const svn_string_t *mine,
+ const svn_string_t *incoming,
+ const svn_string_t *incoming_base,
+ apr_pool_t *result_pool)
+{
+ if (incoming_base == NULL)
+ {
+ /* Attempting to add the value INCOMING. */
+ SVN_ERR_ASSERT_NO_RETURN(incoming != NULL);
+
+ if (mine)
+ {
+ /* To have a conflict, these must be different. */
+ SVN_ERR_ASSERT_NO_RETURN(!svn_string_compare(mine, incoming));
+
+ /* Note that we don't care whether MINE is locally-added or
+ edited, or just something different that is a copy of the
+ pristine ORIGINAL. */
+ return svn_stringbuf_createf(result_pool,
+ _("Trying to add new property '%s'\n"
+ "but the property already exists.\n"),
+ propname);
+ }
+
+ /* To have a conflict, we must have an ORIGINAL which has been
+ locally-deleted. */
+ SVN_ERR_ASSERT_NO_RETURN(original != NULL);
+ return svn_stringbuf_createf(result_pool,
+ _("Trying to add new property '%s'\n"
+ "but the property has been locally "
+ "deleted.\n"),
+ propname);
+ }
+
+ if (incoming == NULL)
+ {
+ /* Attempting to delete the value INCOMING_BASE. */
+ SVN_ERR_ASSERT_NO_RETURN(incoming_base != NULL);
+
+ /* Are we trying to delete a local addition? */
+ if (original == NULL && mine != NULL)
+ return svn_stringbuf_createf(result_pool,
+ _("Trying to delete property '%s'\n"
+ "but the property has been locally "
+ "added.\n"),
+ propname);
+
+ /* A conflict can only occur if we originally had the property;
+ otherwise, we would have merged the property-delete into the
+ non-existent property. */
+ SVN_ERR_ASSERT_NO_RETURN(original != NULL);
+
+ if (svn_string_compare(original, incoming_base))
+ {
+ if (mine)
+ /* We were trying to delete the correct property, but an edit
+ caused the conflict. */
+ return svn_stringbuf_createf(result_pool,
+ _("Trying to delete property '%s'\n"
+ "but the property has been locally "
+ "modified.\n"),
+ propname);
+ }
+ else if (mine == NULL)
+ {
+ /* We were trying to delete the property, but we have locally
+ deleted the same property, but with a different value. */
+ return svn_stringbuf_createf(result_pool,
+ _("Trying to delete property '%s'\n"
+ "but the property has been locally "
+ "deleted and had a different "
+ "value.\n"),
+ propname);
+ }
+
+ /* We were trying to delete INCOMING_BASE but our ORIGINAL is
+ something else entirely. */
+ SVN_ERR_ASSERT_NO_RETURN(!svn_string_compare(original, incoming_base));
+
+ return svn_stringbuf_createf(result_pool,
+ _("Trying to delete property '%s'\n"
+ "but the local property value is "
+ "different.\n"),
+ propname);
+ }
+
+ /* Attempting to change the property from INCOMING_BASE to INCOMING. */
+
+ /* If we have a (current) property value, then it should be different
+ from the INCOMING_BASE; otherwise, the incoming change would have
+ been applied to it. */
+ SVN_ERR_ASSERT_NO_RETURN(!mine || !svn_string_compare(mine, incoming_base));
+
+ if (original && mine && svn_string_compare(original, mine))
+ {
+ /* We have an unchanged property, so the original values must
+ have been different. */
+ SVN_ERR_ASSERT_NO_RETURN(!svn_string_compare(original, incoming_base));
+ return svn_stringbuf_createf(result_pool,
+ _("Trying to change property '%s'\n"
+ "but the local property value conflicts "
+ "with the incoming change.\n"),
+ propname);
+ }
+
+ if (original && mine)
+ return svn_stringbuf_createf(result_pool,
+ _("Trying to change property '%s'\n"
+ "but the property has already been locally "
+ "changed to a different value.\n"),
+ propname);
+
+ if (original)
+ return svn_stringbuf_createf(result_pool,
+ _("Trying to change property '%s'\nbut "
+ "the property has been locally deleted.\n"),
+ propname);
+
+ if (mine)
+ return svn_stringbuf_createf(result_pool,
+ _("Trying to change property '%s'\nbut the "
+ "property has been locally added with a "
+ "different value.\n"),
+ propname);
+
+ return svn_stringbuf_createf(result_pool,
+ _("Trying to change property '%s'\nbut "
+ "the property does not exist locally.\n"),
+ propname);
+}
+
+
+/* SKEL will be one of:
+
+ ()
+ (VALUE)
+
+ Return NULL for the former (the particular property value was not
+ present), and VALUE for the second. */
+static const svn_string_t *
+maybe_prop_value(const svn_skel_t *skel,
+ apr_pool_t *result_pool)
+{
+ if (skel->children == NULL)
+ return NULL;
+
+ return svn_string_ncreate(skel->children->data,
+ skel->children->len,
+ result_pool);
+}
+
+
+/* Parse a property conflict description from the provided SKEL.
+ The result includes a descriptive message (see generate_conflict_message)
+ and maybe a diff of property values containing conflict markers.
+ The result will be allocated in RESULT_POOL.
+
+ Note: SKEL is a single property conflict of the form:
+
+ ("prop" ([ORIGINAL]) ([MINE]) ([INCOMING]) ([INCOMING_BASE]))
+
+ See notes/wc-ng/conflict-storage for more information. */
+static svn_error_t *
+prop_conflict_from_skel(const svn_string_t **conflict_desc,
+ const svn_skel_t *skel,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ const svn_string_t *original;
+ const svn_string_t *mine;
+ const svn_string_t *incoming;
+ const svn_string_t *incoming_base;
+ const char *propname;
+ svn_diff_t *diff;
+ svn_diff_file_options_t *diff_opts;
+ svn_stringbuf_t *buf;
+ svn_boolean_t original_is_binary;
+ svn_boolean_t mine_is_binary;
+ svn_boolean_t incoming_is_binary;
+
+ /* Navigate to the property name. */
+ skel = skel->children->next;
+
+ /* We need to copy these into SCRATCH_POOL in order to nul-terminate
+ the values. */
+ propname = apr_pstrmemdup(scratch_pool, skel->data, skel->len);
+ original = maybe_prop_value(skel->next, scratch_pool);
+ mine = maybe_prop_value(skel->next->next, scratch_pool);
+ incoming = maybe_prop_value(skel->next->next->next, scratch_pool);
+ incoming_base = maybe_prop_value(skel->next->next->next->next, scratch_pool);
+
+ buf = generate_conflict_message(propname, original, mine, incoming,
+ incoming_base, scratch_pool);
+
+ if (mine == NULL)
+ mine = svn_string_create_empty(scratch_pool);
+ if (incoming == NULL)
+ incoming = svn_string_create_empty(scratch_pool);
+
+ /* Pick a suitable base for the conflict diff.
+ * The incoming value is always a change,
+ * but the local value might not have changed. */
+ if (original == NULL)
+ {
+ if (incoming_base)
+ original = incoming_base;
+ else
+ original = svn_string_create_empty(scratch_pool);
+ }
+ else if (incoming_base && svn_string_compare(original, mine))
+ original = incoming_base;
+
+ /* If any of the property values involved in the diff is binary data,
+ * do not generate a diff. */
+ original_is_binary = svn_io_is_binary_data(original->data, original->len);
+ mine_is_binary = svn_io_is_binary_data(mine->data, mine->len);
+ incoming_is_binary = svn_io_is_binary_data(incoming->data, incoming->len);
+
+ if (!(original_is_binary || mine_is_binary || incoming_is_binary))
+ {
+ diff_opts = svn_diff_file_options_create(scratch_pool);
+ diff_opts->ignore_space = svn_diff_file_ignore_space_none;
+ diff_opts->ignore_eol_style = FALSE;
+ diff_opts->show_c_function = FALSE;
+ SVN_ERR(svn_diff_mem_string_diff3(&diff, original, mine, incoming,
+ diff_opts, scratch_pool));
+ if (svn_diff_contains_conflicts(diff))
+ {
+ svn_stream_t *stream;
+ svn_diff_conflict_display_style_t style;
+ const char *mine_marker = _("<<<<<<< (local property value)");
+ const char *incoming_marker = _(">>>>>>> (incoming property value)");
+ const char *separator = "=======";
+ svn_string_t *original_ascii =
+ svn_string_create(svn_utf_cstring_from_utf8_fuzzy(original->data,
+ scratch_pool),
+ scratch_pool);
+ svn_string_t *mine_ascii =
+ svn_string_create(svn_utf_cstring_from_utf8_fuzzy(mine->data,
+ scratch_pool),
+ scratch_pool);
+ svn_string_t *incoming_ascii =
+ svn_string_create(svn_utf_cstring_from_utf8_fuzzy(incoming->data,
+ scratch_pool),
+ scratch_pool);
+
+ style = svn_diff_conflict_display_modified_latest;
+ stream = svn_stream_from_stringbuf(buf, scratch_pool);
+ SVN_ERR(svn_stream_skip(stream, buf->len));
+ SVN_ERR(svn_diff_mem_string_output_merge2(stream, diff,
+ original_ascii,
+ mine_ascii,
+ incoming_ascii,
+ NULL, mine_marker,
+ incoming_marker, separator,
+ style, scratch_pool));
+ SVN_ERR(svn_stream_close(stream));
+
+ *conflict_desc = svn_string_create_from_buf(buf, result_pool);
+ return SVN_NO_ERROR;
+ }
+ }
+
+ /* If we could not print a conflict diff just print full values . */
+ if (mine->len > 0)
+ {
+ svn_stringbuf_appendcstr(buf, _("Local property value:\n"));
+ if (mine_is_binary)
+ svn_stringbuf_appendcstr(buf, _("Cannot display: property value is "
+ "binary data\n"));
+ else
+ svn_stringbuf_appendbytes(buf, mine->data, mine->len);
+ svn_stringbuf_appendcstr(buf, "\n");
+ }
+
+ if (incoming->len > 0)
+ {
+ svn_stringbuf_appendcstr(buf, _("Incoming property value:\n"));
+ if (incoming_is_binary)
+ svn_stringbuf_appendcstr(buf, _("Cannot display: property value is "
+ "binary data\n"));
+ else
+ svn_stringbuf_appendbytes(buf, incoming->data, incoming->len);
+ svn_stringbuf_appendcstr(buf, "\n");
+ }
+
+ *conflict_desc = svn_string_create_from_buf(buf, result_pool);
+ return SVN_NO_ERROR;
+}
+
+
+/* Create a property conflict file at PREJFILE based on the property
+ conflicts in CONFLICT_SKEL. */
+svn_error_t *
+svn_wc__create_prejfile(const char **tmp_prejfile_abspath,
+ svn_wc__db_t *db,
+ const char *local_abspath,
+ const svn_skel_t *conflict_skel,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ const char *tempdir_abspath;
+ svn_stream_t *stream;
+ const char *temp_abspath;
+ apr_pool_t *iterpool = svn_pool_create(scratch_pool);
+ const svn_skel_t *scan;
+
+ SVN_ERR(svn_wc__db_temp_wcroot_tempdir(&tempdir_abspath,
+ db, local_abspath,
+ iterpool, iterpool));
+
+ SVN_ERR(svn_stream_open_unique(&stream, &temp_abspath,
+ tempdir_abspath, svn_io_file_del_none,
+ scratch_pool, iterpool));
+
+ for (scan = conflict_skel->children->next; scan != NULL; scan = scan->next)
+ {
+ svn_pool_clear(iterpool);
+
+ SVN_ERR(append_prop_conflict(stream, scan, iterpool));
+ }
+
+ SVN_ERR(svn_stream_close(stream));
+
+ svn_pool_destroy(iterpool);
+
+ *tmp_prejfile_abspath = apr_pstrdup(result_pool, temp_abspath);
+ return SVN_NO_ERROR;
+}
+
+
+/* Set the value of *STATE to NEW_VALUE if STATE is not NULL
+ * and NEW_VALUE is a higer order value than *STATE's current value
+ * using this ordering (lower order first):
+ *
+ * - unknown, unchanged, inapplicable
+ * - changed
+ * - merged
+ * - missing
+ * - obstructed
+ * - conflicted
+ *
+ */
+static void
+set_prop_merge_state(svn_wc_notify_state_t *state,
+ svn_wc_notify_state_t new_value)
+{
+ static char ordering[] =
+ { svn_wc_notify_state_unknown,
+ svn_wc_notify_state_unchanged,
+ svn_wc_notify_state_inapplicable,
+ svn_wc_notify_state_changed,
+ svn_wc_notify_state_merged,
+ svn_wc_notify_state_obstructed,
+ svn_wc_notify_state_conflicted };
+ int state_pos = 0, i;
+
+ if (! state)
+ return;
+
+ /* Find *STATE in our ordering */
+ for (i = 0; i < sizeof(ordering); i++)
+ {
+ if (*state == ordering[i])
+ {
+ state_pos = i;
+ break;
+ }
+ }
+
+ /* Find NEW_VALUE in our ordering
+ * We don't need to look further than where we found *STATE though:
+ * If we find our value, it's order is too low.
+ * If we don't find it, we'll want to set it, no matter its order.
+ */
+
+ for (i = 0; i <= state_pos; i++)
+ {
+ if (new_value == ordering[i])
+ return;
+ }
+
+ *state = new_value;
+}
+
+/* Apply the addition of a property with name PROPNAME and value NEW_VAL to
+ * the existing property with value WORKING_VAL, that originally had value
+ * PRISTINE_VAL.
+ *
+ * Sets *RESULT_VAL to the resulting value.
+ * Sets *CONFLICT_REMAINS to TRUE if the change caused a conflict.
+ * Sets *DID_MERGE to true if the result is caused by a merge
+ */
+static svn_error_t *
+apply_single_prop_add(const svn_string_t **result_val,
+ svn_boolean_t *conflict_remains,
+ svn_boolean_t *did_merge,
+ const char *propname,
+ const svn_string_t *pristine_val,
+ const svn_string_t *new_val,
+ const svn_string_t *working_val,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+
+{
+ *conflict_remains = FALSE;
+
+ if (working_val)
+ {
+ /* the property already exists in actual_props... */
+
+ if (svn_string_compare(working_val, new_val))
+ /* The value we want is already there, so it's a merge. */
+ *did_merge = TRUE;
+
+ else
+ {
+ svn_boolean_t merged_prop = FALSE;
+
+ /* The WC difference doesn't match the new value.
+ We only merge mergeinfo; other props conflict */
+ if (strcmp(propname, SVN_PROP_MERGEINFO) == 0)
+ {
+ const svn_string_t *merged_val;
+ svn_error_t *err = combine_mergeinfo_props(&merged_val,
+ working_val,
+ new_val,
+ result_pool,
+ scratch_pool);
+
+ /* Issue #3896 'mergeinfo syntax errors should be treated
+ gracefully': If bogus mergeinfo is present we can't
+ merge intelligently, so raise a conflict instead. */
+ if (err)
+ {
+ if (err->apr_err == SVN_ERR_MERGEINFO_PARSE_ERROR)
+ svn_error_clear(err);
+ else
+ return svn_error_trace(err);
+ }
+ else
+ {
+ merged_prop = TRUE;
+ *result_val = merged_val;
+ *did_merge = TRUE;
+ }
+ }
+
+ if (!merged_prop)
+ *conflict_remains = TRUE;
+ }
+ }
+ else if (pristine_val)
+ *conflict_remains = TRUE;
+ else /* property doesn't yet exist in actual_props... */
+ /* so just set it */
+ *result_val = new_val;
+
+ return SVN_NO_ERROR;
+}
+
+
+/* Apply the deletion of a property to the existing
+ * property with value WORKING_VAL, that originally had value PRISTINE_VAL.
+ *
+ * Sets *RESULT_VAL to the resulting value.
+ * Sets *CONFLICT_REMAINS to TRUE if the change caused a conflict.
+ * Sets *DID_MERGE to true if the result is caused by a merge
+ */
+static svn_error_t *
+apply_single_prop_delete(const svn_string_t **result_val,
+ svn_boolean_t *conflict_remains,
+ svn_boolean_t *did_merge,
+ const svn_string_t *base_val,
+ const svn_string_t *old_val,
+ const svn_string_t *working_val)
+{
+ *conflict_remains = FALSE;
+
+ if (! base_val)
+ {
+ if (working_val
+ && !svn_string_compare(working_val, old_val))
+ {
+ /* We are trying to delete a locally-added prop. */
+ *conflict_remains = TRUE;
+ }
+ else
+ {
+ *result_val = NULL;
+ if (old_val)
+ /* This is a merge, merging a delete into non-existent
+ property or a local addition of same prop value. */
+ *did_merge = TRUE;
+ }
+ }
+
+ else if (svn_string_compare(base_val, old_val))
+ {
+ if (working_val)
+ {
+ if (svn_string_compare(working_val, old_val))
+ /* they have the same values, so it's an update */
+ *result_val = NULL;
+ else
+ *conflict_remains = TRUE;
+ }
+ else
+ /* The property is locally deleted from the same value, so it's
+ a merge */
+ *did_merge = TRUE;
+ }
+
+ else
+ *conflict_remains = TRUE;
+
+ return SVN_NO_ERROR;
+}
+
+
+/* Merge a change to the mergeinfo property. Similar to
+ apply_single_prop_change(), except that the property name is always
+ SVN_PROP_MERGEINFO. */
+/* ### This function is extracted straight from the previous all-in-one
+ version of apply_single_prop_change() by removing the code paths that
+ were not followed for this property, but with no attempt to rationalize
+ the remainder. */
+static svn_error_t *
+apply_single_mergeinfo_prop_change(const svn_string_t **result_val,
+ svn_boolean_t *conflict_remains,
+ svn_boolean_t *did_merge,
+ const svn_string_t *base_val,
+ const svn_string_t *old_val,
+ const svn_string_t *new_val,
+ const svn_string_t *working_val,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ if ((working_val && ! base_val)
+ || (! working_val && base_val)
+ || (working_val && base_val
+ && !svn_string_compare(working_val, base_val)))
+ {
+ /* Locally changed property */
+ if (working_val)
+ {
+ if (svn_string_compare(working_val, new_val))
+ /* The new value equals the changed value: a no-op merge */
+ *did_merge = TRUE;
+ else
+ {
+ /* We have base, WC, and new values. Discover
+ deltas between base <-> WC, and base <->
+ incoming. Combine those deltas, and apply
+ them to base to get the new value. */
+ SVN_ERR(combine_forked_mergeinfo_props(&new_val, old_val,
+ working_val,
+ new_val,
+ result_pool,
+ scratch_pool));
+ *result_val = new_val;
+ *did_merge = TRUE;
+ }
+ }
+ else
+ {
+ /* There is a base_val but no working_val */
+ *conflict_remains = TRUE;
+ }
+ }
+
+ else if (! working_val) /* means !working_val && !base_val due
+ to conditions above: no prop at all */
+ {
+ /* Discover any mergeinfo additions in the
+ incoming value relative to the base, and
+ "combine" those with the empty WC value. */
+ svn_mergeinfo_t deleted_mergeinfo, added_mergeinfo;
+ svn_string_t *mergeinfo_string;
+
+ SVN_ERR(diff_mergeinfo_props(&deleted_mergeinfo,
+ &added_mergeinfo,
+ old_val, new_val, scratch_pool));
+ SVN_ERR(svn_mergeinfo_to_string(&mergeinfo_string,
+ added_mergeinfo, result_pool));
+ *result_val = mergeinfo_string;
+ }
+
+ else /* means working && base && svn_string_compare(working, base) */
+ {
+ if (svn_string_compare(old_val, base_val))
+ *result_val = new_val;
+ else
+ {
+ /* We have base, WC, and new values. Discover
+ deltas between base <-> WC, and base <->
+ incoming. Combine those deltas, and apply
+ them to base to get the new value. */
+ SVN_ERR(combine_forked_mergeinfo_props(&new_val, old_val,
+ working_val,
+ new_val, result_pool,
+ scratch_pool));
+ *result_val = new_val;
+ *did_merge = TRUE;
+ }
+ }
+
+ return SVN_NO_ERROR;
+}
+
+/* Merge a change to a property, using the rule that if the working value
+ is equal to the new value then there is nothing we need to do. Else, if
+ the working value is the same as the old value then apply the change as a
+ simple update (replacement), otherwise invoke maybe_generate_propconflict().
+ The definition of the arguments and behaviour is the same as
+ apply_single_prop_change(). */
+static svn_error_t *
+apply_single_generic_prop_change(const svn_string_t **result_val,
+ svn_boolean_t *conflict_remains,
+ svn_boolean_t *did_merge,
+ const svn_string_t *old_val,
+ const svn_string_t *new_val,
+ const svn_string_t *working_val)
+{
+ SVN_ERR_ASSERT(old_val != NULL);
+
+ /* If working_val is the same as new_val already then there is
+ * nothing to do */
+ if (working_val && new_val
+ && svn_string_compare(working_val, new_val))
+ {
+ /* All values identical is a trivial, non-notifiable merge */
+ if (! old_val || ! svn_string_compare(old_val, new_val))
+ *did_merge = TRUE;
+ }
+ /* If working_val is the same as old_val... */
+ else if (working_val && old_val
+ && svn_string_compare(working_val, old_val))
+ {
+ /* A trivial update: change it to new_val. */
+ *result_val = new_val;
+ }
+ else
+ {
+ /* Merge the change. */
+ *conflict_remains = TRUE;
+ }
+
+ return SVN_NO_ERROR;
+}
+
+/* Change the property with name PROPNAME, setting *RESULT_VAL,
+ * *CONFLICT_REMAINS and *DID_MERGE according to the merge outcome.
+ *
+ * BASE_VAL contains the working copy base property value. (May be null.)
+ *
+ * OLD_VAL contains the value of the property the server
+ * thinks it's overwriting. (Not null.)
+ *
+ * NEW_VAL contains the value to be set. (Not null.)
+ *
+ * WORKING_VAL contains the working copy actual value. (May be null.)
+ */
+static svn_error_t *
+apply_single_prop_change(const svn_string_t **result_val,
+ svn_boolean_t *conflict_remains,
+ svn_boolean_t *did_merge,
+ const char *propname,
+ const svn_string_t *base_val,
+ const svn_string_t *old_val,
+ const svn_string_t *new_val,
+ const svn_string_t *working_val,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ svn_boolean_t merged_prop = FALSE;
+
+ *conflict_remains = FALSE;
+
+ /* Note: The purpose is to apply the change (old_val -> new_val) onto
+ (working_val). There is no need for base_val to be involved in the
+ process except as a bit of context to help the user understand and
+ resolve any conflict. */
+
+ /* Decide how to merge, based on whether we know anything special about
+ the property. */
+ if (strcmp(propname, SVN_PROP_MERGEINFO) == 0)
+ {
+ /* We know how to merge any mergeinfo property change...
+
+ ...But Issue #3896 'mergeinfo syntax errors should be treated
+ gracefully' might thwart us. If bogus mergeinfo is present we
+ can't merge intelligently, so let the standard method deal with
+ it instead. */
+ svn_error_t *err = apply_single_mergeinfo_prop_change(result_val,
+ conflict_remains,
+ did_merge,
+ base_val,
+ old_val,
+ new_val,
+ working_val,
+ result_pool,
+ scratch_pool);
+ if (err)
+ {
+ if (err->apr_err == SVN_ERR_MERGEINFO_PARSE_ERROR)
+ svn_error_clear(err);
+ else
+ return svn_error_trace(err);
+ }
+ else
+ {
+ merged_prop = TRUE;
+ }
+ }
+
+ if (!merged_prop)
+ {
+ /* The standard method: perform a simple update automatically, but
+ pass any other kind of merge to maybe_generate_propconflict(). */
+
+ SVN_ERR(apply_single_generic_prop_change(result_val, conflict_remains,
+ did_merge,
+ old_val, new_val, working_val));
+ }
+
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_wc__merge_props(svn_skel_t **conflict_skel,
+ svn_wc_notify_state_t *state,
+ apr_hash_t **new_actual_props,
+ svn_wc__db_t *db,
+ const char *local_abspath,
+ apr_hash_t *server_baseprops,
+ apr_hash_t *pristine_props,
+ apr_hash_t *actual_props,
+ const apr_array_header_t *propchanges,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ apr_pool_t *iterpool;
+ int i;
+ apr_hash_t *conflict_props = NULL;
+ apr_hash_t *their_props;
+
+ SVN_ERR_ASSERT(pristine_props != NULL);
+ SVN_ERR_ASSERT(actual_props != NULL);
+
+ *new_actual_props = apr_hash_copy(result_pool, actual_props);
+
+ if (!server_baseprops)
+ server_baseprops = pristine_props;
+
+ their_props = apr_hash_copy(scratch_pool, server_baseprops);
+
+ if (state)
+ {
+ /* Start out assuming no changes or conflicts. Don't bother to
+ examine propchanges->nelts yet; even if we knew there were
+ propchanges, we wouldn't yet know if they are "normal" props,
+ as opposed wc or entry props. */
+ *state = svn_wc_notify_state_unchanged;
+ }
+
+ /* Looping over the array of incoming propchanges we want to apply: */
+ iterpool = svn_pool_create(scratch_pool);
+ for (i = 0; i < propchanges->nelts; i++)
+ {
+ const svn_prop_t *incoming_change
+ = &APR_ARRAY_IDX(propchanges, i, svn_prop_t);
+ const char *propname = incoming_change->name;
+ const svn_string_t *base_val /* Pristine in WC */
+ = svn_hash_gets(pristine_props, propname);
+ const svn_string_t *from_val /* Merge left */
+ = svn_hash_gets(server_baseprops, propname);
+ const svn_string_t *to_val /* Merge right */
+ = incoming_change->value;
+ const svn_string_t *working_val /* Mine */
+ = svn_hash_gets(actual_props, propname);
+ const svn_string_t *result_val;
+ svn_boolean_t conflict_remains;
+ svn_boolean_t did_merge = FALSE;
+
+ svn_pool_clear(iterpool);
+
+ to_val = to_val ? svn_string_dup(to_val, result_pool) : NULL;
+
+ svn_hash_sets(their_props, propname, to_val);
+
+
+ /* We already know that state is at least `changed', so mark
+ that, but remember that we may later upgrade to `merged' or
+ even `conflicted'. */
+ set_prop_merge_state(state, svn_wc_notify_state_changed);
+
+ result_val = working_val;
+
+ if (! from_val) /* adding a new property */
+ SVN_ERR(apply_single_prop_add(&result_val, &conflict_remains,
+ &did_merge, propname,
+ base_val, to_val, working_val,
+ result_pool, iterpool));
+
+ else if (! to_val) /* delete an existing property */
+ SVN_ERR(apply_single_prop_delete(&result_val, &conflict_remains,
+ &did_merge,
+ base_val, from_val, working_val));
+
+ else /* changing an existing property */
+ SVN_ERR(apply_single_prop_change(&result_val, &conflict_remains,
+ &did_merge, propname,
+ base_val, from_val, to_val, working_val,
+ result_pool, iterpool));
+
+ if (result_val != working_val)
+ svn_hash_sets(*new_actual_props, propname, result_val);
+ if (did_merge)
+ set_prop_merge_state(state, svn_wc_notify_state_merged);
+
+ /* merging logic complete, now we need to possibly log conflict
+ data to tmpfiles. */
+
+ if (conflict_remains)
+ {
+ set_prop_merge_state(state, svn_wc_notify_state_conflicted);
+
+ if (!conflict_props)
+ conflict_props = apr_hash_make(scratch_pool);
+
+ svn_hash_sets(conflict_props, propname, "");
+ }
+
+ } /* foreach propchange ... */
+ svn_pool_destroy(iterpool);
+
+ /* Finished applying all incoming propchanges to our hashes! */
+
+ if (conflict_props != NULL)
+ {
+ /* Ok, we got some conflict. Lets store all the property knowledge we
+ have for resolving later */
+
+ if (!*conflict_skel)
+ *conflict_skel = svn_wc__conflict_skel_create(result_pool);
+
+ SVN_ERR(svn_wc__conflict_skel_add_prop_conflict(*conflict_skel,
+ db, local_abspath,
+ NULL /* reject_path */,
+ actual_props,
+ server_baseprops,
+ their_props,
+ conflict_props,
+ result_pool,
+ scratch_pool));
+ }
+
+ return SVN_NO_ERROR;
+}
+
+
+/* Set a single 'wcprop' NAME to VALUE for versioned object LOCAL_ABSPATH.
+ If VALUE is null, remove property NAME. */
+static svn_error_t *
+wcprop_set(svn_wc__db_t *db,
+ const char *local_abspath,
+ const char *name,
+ const svn_string_t *value,
+ apr_pool_t *scratch_pool)
+{
+ apr_hash_t *prophash;
+
+ SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath));
+
+ /* Note: this is not well-transacted. But... meh. This is merely a cache,
+ and if two processes are trying to modify this one entry at the same
+ time, then fine: we can let one be a winner, and one a loser. Of course,
+ if there are *other* state changes afoot, then the lack of a txn could
+ be a real issue, but we cannot solve that here. */
+
+ SVN_ERR(svn_wc__db_base_get_dav_cache(&prophash, db, local_abspath,
+ scratch_pool, scratch_pool));
+
+ if (prophash == NULL)
+ prophash = apr_hash_make(scratch_pool);
+
+ svn_hash_sets(prophash, name, value);
+ return svn_error_trace(svn_wc__db_base_set_dav_cache(db, local_abspath,
+ prophash,
+ scratch_pool));
+}
+
+
+svn_error_t *
+svn_wc__get_actual_props(apr_hash_t **props,
+ svn_wc__db_t *db,
+ const char *local_abspath,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ SVN_ERR_ASSERT(props != NULL);
+ SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath));
+
+ /* ### perform some state checking. for example, locally-deleted nodes
+ ### should not have any ACTUAL props. */
+
+ return svn_error_trace(svn_wc__db_read_props(props, db, local_abspath,
+ result_pool, scratch_pool));
+}
+
+
+svn_error_t *
+svn_wc_prop_list2(apr_hash_t **props,
+ svn_wc_context_t *wc_ctx,
+ const char *local_abspath,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ return svn_error_trace(svn_wc__get_actual_props(props,
+ wc_ctx->db,
+ local_abspath,
+ result_pool,
+ scratch_pool));
+}
+
+struct propname_filter_baton_t {
+ svn_wc__proplist_receiver_t receiver_func;
+ void *receiver_baton;
+ const char *propname;
+};
+
+static svn_error_t *
+propname_filter_receiver(void *baton,
+ const char *local_abspath,
+ apr_hash_t *props,
+ apr_pool_t *scratch_pool)
+{
+ struct propname_filter_baton_t *pfb = baton;
+ const svn_string_t *propval = svn_hash_gets(props, pfb->propname);
+
+ if (propval)
+ {
+ props = apr_hash_make(scratch_pool);
+ svn_hash_sets(props, pfb->propname, propval);
+
+ SVN_ERR(pfb->receiver_func(pfb->receiver_baton, local_abspath, props,
+ scratch_pool));
+ }
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_wc__prop_list_recursive(svn_wc_context_t *wc_ctx,
+ const char *local_abspath,
+ const char *propname,
+ svn_depth_t depth,
+ svn_boolean_t pristine,
+ const apr_array_header_t *changelists,
+ svn_wc__proplist_receiver_t receiver_func,
+ void *receiver_baton,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ apr_pool_t *scratch_pool)
+{
+ svn_wc__proplist_receiver_t receiver = receiver_func;
+ void *baton = receiver_baton;
+ struct propname_filter_baton_t pfb;
+
+ pfb.receiver_func = receiver_func;
+ pfb.receiver_baton = receiver_baton;
+ pfb.propname = propname;
+
+ SVN_ERR_ASSERT(receiver_func);
+
+ if (propname)
+ {
+ baton = &pfb;
+ receiver = propname_filter_receiver;
+ }
+
+ switch (depth)
+ {
+ case svn_depth_empty:
+ {
+ apr_hash_t *props;
+ apr_hash_t *changelist_hash = NULL;
+
+ if (changelists && changelists->nelts)
+ SVN_ERR(svn_hash_from_cstring_keys(&changelist_hash,
+ changelists, scratch_pool));
+
+ if (!svn_wc__internal_changelist_match(wc_ctx->db, local_abspath,
+ changelist_hash, scratch_pool))
+ break;
+
+ if (pristine)
+ SVN_ERR(svn_wc__db_read_pristine_props(&props, wc_ctx->db,
+ local_abspath,
+ scratch_pool, scratch_pool));
+ else
+ SVN_ERR(svn_wc__db_read_props(&props, wc_ctx->db, local_abspath,
+ scratch_pool, scratch_pool));
+
+ if (props && apr_hash_count(props) > 0)
+ SVN_ERR(receiver(baton, local_abspath, props, scratch_pool));
+ }
+ break;
+ case svn_depth_files:
+ case svn_depth_immediates:
+ case svn_depth_infinity:
+ {
+ SVN_ERR(svn_wc__db_read_props_streamily(wc_ctx->db, local_abspath,
+ depth, pristine,
+ changelists, receiver, baton,
+ cancel_func, cancel_baton,
+ scratch_pool));
+ }
+ break;
+ default:
+ SVN_ERR_MALFUNCTION();
+ }
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_wc__prop_retrieve_recursive(apr_hash_t **values,
+ svn_wc_context_t *wc_ctx,
+ const char *local_abspath,
+ const char *propname,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ return svn_error_trace(
+ svn_wc__db_prop_retrieve_recursive(values,
+ wc_ctx->db,
+ local_abspath,
+ propname,
+ result_pool, scratch_pool));
+}
+
+svn_error_t *
+svn_wc_get_pristine_props(apr_hash_t **props,
+ svn_wc_context_t *wc_ctx,
+ const char *local_abspath,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ svn_error_t *err;
+
+ SVN_ERR_ASSERT(props != NULL);
+ SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath));
+
+ /* Certain node stats do not have properties defined on them. Check the
+ state, and return NULL for these situations. */
+
+ err = svn_wc__db_read_pristine_props(props, wc_ctx->db, local_abspath,
+ result_pool, scratch_pool);
+
+ if (err)
+ {
+ if (err->apr_err != SVN_ERR_WC_PATH_UNEXPECTED_STATUS)
+ return svn_error_trace(err);
+
+ svn_error_clear(err);
+
+ /* Documented behavior is to set *PROPS to NULL */
+ *props = NULL;
+ }
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_wc_prop_get2(const svn_string_t **value,
+ svn_wc_context_t *wc_ctx,
+ const char *local_abspath,
+ const char *name,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ enum svn_prop_kind kind = svn_property_kind2(name);
+ svn_error_t *err;
+
+ SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath));
+
+ if (kind == svn_prop_entry_kind)
+ {
+ /* we don't do entry properties here */
+ return svn_error_createf(SVN_ERR_BAD_PROP_KIND, NULL,
+ _("Property '%s' is an entry property"), name);
+ }
+
+ err = svn_wc__internal_propget(value, wc_ctx->db, local_abspath, name,
+ result_pool, scratch_pool);
+
+ if (err)
+ {
+ if (err->apr_err != SVN_ERR_WC_PATH_UNEXPECTED_STATUS)
+ return svn_error_trace(err);
+
+ svn_error_clear(err);
+ /* Documented behavior is to set *VALUE to NULL */
+ *value = NULL;
+ }
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_wc__internal_propget(const svn_string_t **value,
+ svn_wc__db_t *db,
+ const char *local_abspath,
+ const char *name,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ apr_hash_t *prophash = NULL;
+ enum svn_prop_kind kind = svn_property_kind2(name);
+
+ SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath));
+ SVN_ERR_ASSERT(kind != svn_prop_entry_kind);
+
+ if (kind == svn_prop_wc_kind)
+ {
+ SVN_ERR_W(svn_wc__db_base_get_dav_cache(&prophash, db, local_abspath,
+ result_pool, scratch_pool),
+ _("Failed to load properties"));
+ }
+ else
+ {
+ /* regular prop */
+ SVN_ERR_W(svn_wc__get_actual_props(&prophash, db, local_abspath,
+ result_pool, scratch_pool),
+ _("Failed to load properties"));
+ }
+
+ if (prophash)
+ *value = svn_hash_gets(prophash, name);
+ else
+ *value = NULL;
+
+ return SVN_NO_ERROR;
+}
+
+
+/* The special Subversion properties are not valid for all node kinds.
+ Return an error if NAME is an invalid Subversion property for PATH which
+ is of kind NODE_KIND. NAME must be in the "svn:" name space.
+
+ Note that we only disallow the property if we're sure it's one that
+ already has a meaning for a different node kind. We don't disallow
+ setting an *unknown* svn: prop here, at this level; a higher level
+ should disallow that if desired.
+ */
+static svn_error_t *
+validate_prop_against_node_kind(const char *name,
+ const char *path,
+ svn_node_kind_t node_kind,
+ apr_pool_t *pool)
+{
+ const char *path_display
+ = svn_path_is_url(path) ? path : svn_dirent_local_style(path, pool);
+
+ switch (node_kind)
+ {
+ case svn_node_dir:
+ if (! svn_prop_is_known_svn_dir_prop(name)
+ && svn_prop_is_known_svn_file_prop(name))
+ return svn_error_createf(SVN_ERR_ILLEGAL_TARGET, NULL,
+ _("Cannot set '%s' on a directory ('%s')"),
+ name, path_display);
+ break;
+ case svn_node_file:
+ if (! svn_prop_is_known_svn_file_prop(name)
+ && svn_prop_is_known_svn_dir_prop(name))
+ return svn_error_createf(SVN_ERR_ILLEGAL_TARGET, NULL,
+ _("Cannot set '%s' on a file ('%s')"),
+ name,
+ path_display);
+ break;
+ default:
+ return svn_error_createf(SVN_ERR_NODE_UNEXPECTED_KIND, NULL,
+ _("'%s' is not a file or directory"),
+ path_display);
+ }
+
+ return SVN_NO_ERROR;
+}
+
+
+struct getter_baton {
+ const svn_string_t *mime_type;
+ const char *local_abspath;
+};
+
+
+/* Provide the MIME_TYPE and/or push the content to STREAM for the file
+ * referenced by (getter_baton *) BATON.
+ *
+ * Implements svn_wc_canonicalize_svn_prop_get_file_t. */
+static svn_error_t *
+get_file_for_validation(const svn_string_t **mime_type,
+ svn_stream_t *stream,
+ void *baton,
+ apr_pool_t *pool)
+{
+ struct getter_baton *gb = baton;
+
+ if (mime_type)
+ *mime_type = gb->mime_type;
+
+ if (stream)
+ {
+ svn_stream_t *read_stream;
+
+ /* Copy the text of GB->LOCAL_ABSPATH into STREAM. */
+ SVN_ERR(svn_stream_open_readonly(&read_stream, gb->local_abspath,
+ pool, pool));
+ SVN_ERR(svn_stream_copy3(read_stream, svn_stream_disown(stream, pool),
+ NULL, NULL, pool));
+ }
+
+ return SVN_NO_ERROR;
+}
+
+
+/* Validate that a file has a 'non-binary' MIME type and contains
+ * self-consistent line endings. If not, then return an error.
+ *
+ * Call GETTER (which must not be NULL) with GETTER_BATON to get the
+ * file's MIME type and/or content. If the MIME type is non-null and
+ * is categorized as 'binary' then return an error and do not request
+ * the file content.
+ *
+ * Use PATH (a local path or a URL) only for error messages.
+ */
+static svn_error_t *
+validate_eol_prop_against_file(const char *path,
+ svn_wc_canonicalize_svn_prop_get_file_t getter,
+ void *getter_baton,
+ apr_pool_t *pool)
+{
+ svn_stream_t *translating_stream;
+ svn_error_t *err;
+ const svn_string_t *mime_type;
+ const char *path_display
+ = svn_path_is_url(path) ? path : svn_dirent_local_style(path, pool);
+
+ /* First just ask the "getter" for the MIME type. */
+ SVN_ERR(getter(&mime_type, NULL, getter_baton, pool));
+
+ /* See if this file has been determined to be binary. */
+ if (mime_type && svn_mime_type_is_binary(mime_type->data))
+ return svn_error_createf
+ (SVN_ERR_ILLEGAL_TARGET, NULL,
+ _("Can't set '%s': "
+ "file '%s' has binary mime type property"),
+ SVN_PROP_EOL_STYLE, path_display);
+
+ /* Now ask the getter for the contents of the file; this will do a
+ newline translation. All we really care about here is whether or
+ not the function fails on inconsistent line endings. The
+ function is "translating" to an empty stream. This is
+ sneeeeeeeeeeeaky. */
+ translating_stream = svn_subst_stream_translated(svn_stream_empty(pool),
+ "", FALSE, NULL, FALSE,
+ pool);
+
+ err = getter(NULL, translating_stream, getter_baton, pool);
+
+ err = svn_error_compose_create(err, svn_stream_close(translating_stream));
+
+ if (err && err->apr_err == SVN_ERR_IO_INCONSISTENT_EOL)
+ return svn_error_createf(SVN_ERR_ILLEGAL_TARGET, err,
+ _("File '%s' has inconsistent newlines"),
+ path_display);
+
+ return svn_error_trace(err);
+}
+
+static svn_error_t *
+do_propset(svn_wc__db_t *db,
+ const char *local_abspath,
+ svn_node_kind_t kind,
+ const char *name,
+ const svn_string_t *value,
+ svn_boolean_t skip_checks,
+ svn_wc_notify_func2_t notify_func,
+ void *notify_baton,
+ apr_pool_t *scratch_pool)
+{
+ apr_hash_t *prophash;
+ svn_wc_notify_action_t notify_action;
+ svn_skel_t *work_item = NULL;
+ svn_boolean_t clear_recorded_info = FALSE;
+
+ SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath));
+
+ SVN_ERR_W(svn_wc__db_read_props(&prophash, db, local_abspath,
+ scratch_pool, scratch_pool),
+ _("Failed to load current properties"));
+
+ /* Setting an inappropriate property is not allowed (unless
+ overridden by 'skip_checks', in some circumstances). Deleting an
+ inappropriate property is allowed, however, since older clients
+ allowed (and other clients possibly still allow) setting it in
+ the first place. */
+ if (value && svn_prop_is_svn_prop(name))
+ {
+ const svn_string_t *new_value;
+ struct getter_baton gb;
+
+ gb.mime_type = svn_hash_gets(prophash, SVN_PROP_MIME_TYPE);
+ gb.local_abspath = local_abspath;
+
+ SVN_ERR(svn_wc_canonicalize_svn_prop(&new_value, name, value,
+ local_abspath, kind,
+ skip_checks,
+ get_file_for_validation, &gb,
+ scratch_pool));
+ value = new_value;
+ }
+
+ if (kind == svn_node_file
+ && (strcmp(name, SVN_PROP_EXECUTABLE) == 0
+ || strcmp(name, SVN_PROP_NEEDS_LOCK) == 0))
+ {
+ SVN_ERR(svn_wc__wq_build_sync_file_flags(&work_item, db, local_abspath,
+ scratch_pool, scratch_pool));
+ }
+
+ /* If we're changing this file's list of expanded keywords, then
+ * we'll need to invalidate its text timestamp, since keyword
+ * expansion affects the comparison of working file to text base.
+ *
+ * Here we retrieve the old list of expanded keywords; after the
+ * property is set, we'll grab the new list and see if it differs
+ * from the old one.
+ */
+ if (kind == svn_node_file && strcmp(name, SVN_PROP_KEYWORDS) == 0)
+ {
+ svn_string_t *old_value = svn_hash_gets(prophash, SVN_PROP_KEYWORDS);
+ apr_hash_t *old_keywords, *new_keywords;
+
+ if (old_value)
+ SVN_ERR(svn_wc__expand_keywords(&old_keywords,
+ db, local_abspath, NULL,
+ old_value->data, TRUE,
+ scratch_pool, scratch_pool));
+ else
+ old_keywords = apr_hash_make(scratch_pool);
+
+ if (value)
+ SVN_ERR(svn_wc__expand_keywords(&new_keywords,
+ db, local_abspath, NULL,
+ value->data, TRUE,
+ scratch_pool, scratch_pool));
+ else
+ new_keywords = apr_hash_make(scratch_pool);
+
+ if (svn_subst_keywords_differ2(old_keywords, new_keywords, FALSE,
+ scratch_pool))
+ {
+ /* If the keywords have changed, then the translation of the file
+ may be different. We should invalidate the RECORDED_SIZE
+ and RECORDED_TIME on this node.
+
+ Note that we don't immediately re-translate the file. But a
+ "has it changed?" check in the future will do a translation
+ from the pristine, and it will want to compare the (new)
+ resulting RECORDED_SIZE against the working copy file.
+
+ Also, when this file is (de)translated with the new keywords,
+ then it could be different, relative to the pristine. We want
+ to ensure the RECORDED_TIME is different, to indicate that
+ a full detranslate/compare is performed. */
+ clear_recorded_info = TRUE;
+ }
+ }
+ else if (kind == svn_node_file && strcmp(name, SVN_PROP_EOL_STYLE) == 0)
+ {
+ svn_string_t *old_value = svn_hash_gets(prophash, SVN_PROP_EOL_STYLE);
+
+ if (((value == NULL) != (old_value == NULL))
+ || (value && ! svn_string_compare(value, old_value)))
+ {
+ clear_recorded_info = TRUE;
+ }
+ }
+
+ /* Find out what type of property change we are doing: add, modify, or
+ delete. */
+ if (svn_hash_gets(prophash, name) == NULL)
+ {
+ if (value == NULL)
+ /* Deleting a non-existent property. */
+ notify_action = svn_wc_notify_property_deleted_nonexistent;
+ else
+ /* Adding a property. */
+ notify_action = svn_wc_notify_property_added;
+ }
+ else
+ {
+ if (value == NULL)
+ /* Deleting the property. */
+ notify_action = svn_wc_notify_property_deleted;
+ else
+ /* Modifying property. */
+ notify_action = svn_wc_notify_property_modified;
+ }
+
+ /* Now we have all the properties in our hash. Simply merge the new
+ property into it. */
+ svn_hash_sets(prophash, name, value);
+
+ /* Drop it right into the db.. */
+ SVN_ERR(svn_wc__db_op_set_props(db, local_abspath, prophash,
+ clear_recorded_info, NULL, work_item,
+ scratch_pool));
+
+ /* Run our workqueue item for sync'ing flags with props. */
+ if (work_item)
+ SVN_ERR(svn_wc__wq_run(db, local_abspath, NULL, NULL, scratch_pool));
+
+ if (notify_func)
+ {
+ svn_wc_notify_t *notify = svn_wc_create_notify(local_abspath,
+ notify_action,
+ scratch_pool);
+ notify->prop_name = name;
+ notify->kind = kind;
+
+ (*notify_func)(notify_baton, notify, scratch_pool);
+ }
+
+ return SVN_NO_ERROR;
+}
+
+/* A baton for propset_walk_cb. */
+struct propset_walk_baton
+{
+ const char *propname; /* The name of the property to set. */
+ const svn_string_t *propval; /* The value to set. */
+ svn_wc__db_t *db; /* Database for the tree being walked. */
+ svn_boolean_t force; /* True iff force was passed. */
+ svn_wc_notify_func2_t notify_func;
+ void *notify_baton;
+};
+
+/* An node-walk callback for svn_wc_prop_set4().
+ *
+ * For LOCAL_ABSPATH, set the property named wb->PROPNAME to the value
+ * wb->PROPVAL, where "wb" is the WALK_BATON of type "struct
+ * propset_walk_baton *".
+ */
+static svn_error_t *
+propset_walk_cb(const char *local_abspath,
+ svn_node_kind_t kind,
+ void *walk_baton,
+ apr_pool_t *scratch_pool)
+{
+ struct propset_walk_baton *wb = walk_baton;
+ svn_error_t *err;
+
+ err = do_propset(wb->db, local_abspath, kind, wb->propname, wb->propval,
+ wb->force, wb->notify_func, wb->notify_baton, scratch_pool);
+ if (err && (err->apr_err == SVN_ERR_ILLEGAL_TARGET
+ || err->apr_err == SVN_ERR_WC_PATH_UNEXPECTED_STATUS))
+ {
+ svn_error_clear(err);
+ err = SVN_NO_ERROR;
+ }
+
+ return svn_error_trace(err);
+}
+
+svn_error_t *
+svn_wc_prop_set4(svn_wc_context_t *wc_ctx,
+ const char *local_abspath,
+ const char *name,
+ const svn_string_t *value,
+ svn_depth_t depth,
+ svn_boolean_t skip_checks,
+ const apr_array_header_t *changelist_filter,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ svn_wc_notify_func2_t notify_func,
+ void *notify_baton,
+ apr_pool_t *scratch_pool)
+{
+ enum svn_prop_kind prop_kind = svn_property_kind2(name);
+ svn_wc__db_status_t status;
+ svn_node_kind_t kind;
+ svn_wc__db_t *db = wc_ctx->db;
+
+ /* we don't do entry properties here */
+ if (prop_kind == svn_prop_entry_kind)
+ return svn_error_createf(SVN_ERR_BAD_PROP_KIND, NULL,
+ _("Property '%s' is an entry property"), name);
+
+ /* Check to see if we're setting the dav cache. */
+ if (prop_kind == svn_prop_wc_kind)
+ {
+ SVN_ERR_ASSERT(depth == svn_depth_empty);
+ return svn_error_trace(wcprop_set(wc_ctx->db, local_abspath,
+ name, value, scratch_pool));
+ }
+
+ SVN_ERR(svn_wc__db_read_info(&status, &kind, NULL, NULL, NULL, NULL, NULL,
+ NULL, NULL, NULL, NULL, NULL, NULL, NULL,
+ NULL, NULL, NULL, NULL, NULL, NULL, NULL,
+ NULL, NULL, NULL, NULL, NULL, NULL,
+ wc_ctx->db, local_abspath,
+ scratch_pool, scratch_pool));
+
+ if (status != svn_wc__db_status_normal
+ && status != svn_wc__db_status_added
+ && status != svn_wc__db_status_incomplete)
+ {
+ return svn_error_createf(SVN_ERR_WC_INVALID_SCHEDULE, NULL,
+ _("Can't set properties on '%s':"
+ " invalid status for updating properties."),
+ svn_dirent_local_style(local_abspath,
+ scratch_pool));
+ }
+
+ /* We have to do this little DIR_ABSPATH dance for backwards compat.
+ But from 1.7 onwards, all locks are of infinite depth, and from 1.6
+ backward we never call this API with depth > empty, so we only need
+ to do the write check once per call, here (and not for every node in
+ the node walker).
+
+ ### Note that we could check for a write lock on local_abspath first
+ ### if we would want to. And then justy check for kind if that fails.
+ ### ... but we need kind for the "svn:" property checks anyway */
+ {
+ const char *dir_abspath;
+
+ if (kind == svn_node_dir)
+ dir_abspath = local_abspath;
+ else
+ dir_abspath = svn_dirent_dirname(local_abspath, scratch_pool);
+
+ /* Verify that we're holding this directory's write lock. */
+ SVN_ERR(svn_wc__write_check(db, dir_abspath, scratch_pool));
+ }
+
+ if (depth == svn_depth_empty || kind != svn_node_dir)
+ {
+ apr_hash_t *changelist_hash = NULL;
+
+ if (changelist_filter && changelist_filter->nelts)
+ SVN_ERR(svn_hash_from_cstring_keys(&changelist_hash, changelist_filter,
+ scratch_pool));
+
+ if (!svn_wc__internal_changelist_match(wc_ctx->db, local_abspath,
+ changelist_hash, scratch_pool))
+ return SVN_NO_ERROR;
+
+ SVN_ERR(do_propset(wc_ctx->db, local_abspath,
+ kind == svn_node_dir
+ ? svn_node_dir
+ : svn_node_file,
+ name, value, skip_checks,
+ notify_func, notify_baton, scratch_pool));
+
+ }
+ else
+ {
+ struct propset_walk_baton wb;
+
+ wb.propname = name;
+ wb.propval = value;
+ wb.db = wc_ctx->db;
+ wb.force = skip_checks;
+ wb.notify_func = notify_func;
+ wb.notify_baton = notify_baton;
+
+ SVN_ERR(svn_wc__internal_walk_children(wc_ctx->db, local_abspath,
+ FALSE, changelist_filter,
+ propset_walk_cb, &wb,
+ depth,
+ cancel_func, cancel_baton,
+ scratch_pool));
+ }
+
+ return SVN_NO_ERROR;
+}
+
+/* Check that NAME names a regular prop. Return an error if it names an
+ * entry prop or a WC prop. */
+static svn_error_t *
+ensure_prop_is_regular_kind(const char *name)
+{
+ enum svn_prop_kind prop_kind = svn_property_kind2(name);
+
+ /* we don't do entry properties here */
+ if (prop_kind == svn_prop_entry_kind)
+ return svn_error_createf(SVN_ERR_BAD_PROP_KIND, NULL,
+ _("Property '%s' is an entry property"), name);
+
+ /* Check to see if we're setting the dav cache. */
+ if (prop_kind == svn_prop_wc_kind)
+ return svn_error_createf(SVN_ERR_BAD_PROP_KIND, NULL,
+ _("Property '%s' is a WC property, not "
+ "a regular property"), name);
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_wc__canonicalize_props(apr_hash_t **prepared_props,
+ const char *local_abspath,
+ svn_node_kind_t node_kind,
+ const apr_hash_t *props,
+ svn_boolean_t skip_some_checks,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ const svn_string_t *mime_type;
+ struct getter_baton gb;
+ apr_hash_index_t *hi;
+
+ /* While we allocate new parts of *PREPARED_PROPS in RESULT_POOL, we
+ don't promise to deep-copy the unchanged keys and values. */
+ *prepared_props = apr_hash_make(result_pool);
+
+ /* Before we can canonicalize svn:eol-style we need to know svn:mime-type,
+ * so process that first. */
+ mime_type = svn_hash_gets((apr_hash_t *)props, SVN_PROP_MIME_TYPE);
+ if (mime_type)
+ {
+ SVN_ERR(svn_wc_canonicalize_svn_prop(
+ &mime_type, SVN_PROP_MIME_TYPE, mime_type,
+ local_abspath, node_kind, skip_some_checks,
+ NULL, NULL, scratch_pool));
+ svn_hash_sets(*prepared_props, SVN_PROP_MIME_TYPE, mime_type);
+ }
+
+ /* Set up the context for canonicalizing the other properties. */
+ gb.mime_type = mime_type;
+ gb.local_abspath = local_abspath;
+
+ /* Check and canonicalize the other properties. */
+ for (hi = apr_hash_first(scratch_pool, (apr_hash_t *)props); hi;
+ hi = apr_hash_next(hi))
+ {
+ const char *name = svn__apr_hash_index_key(hi);
+ const svn_string_t *value = svn__apr_hash_index_val(hi);
+
+ if (strcmp(name, SVN_PROP_MIME_TYPE) == 0)
+ continue;
+
+ SVN_ERR(ensure_prop_is_regular_kind(name));
+ SVN_ERR(svn_wc_canonicalize_svn_prop(
+ &value, name, value,
+ local_abspath, node_kind, skip_some_checks,
+ get_file_for_validation, &gb, scratch_pool));
+ svn_hash_sets(*prepared_props, name, value);
+ }
+
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_wc_canonicalize_svn_prop(const svn_string_t **propval_p,
+ const char *propname,
+ const svn_string_t *propval,
+ const char *path,
+ svn_node_kind_t kind,
+ svn_boolean_t skip_some_checks,
+ svn_wc_canonicalize_svn_prop_get_file_t getter,
+ void *getter_baton,
+ apr_pool_t *pool)
+{
+ svn_stringbuf_t *new_value = NULL;
+
+ /* Keep this static, it may get stored (for read-only purposes) in a
+ hash that outlives this function. */
+ static const svn_string_t boolean_value =
+ {
+ SVN_PROP_BOOLEAN_TRUE,
+ sizeof(SVN_PROP_BOOLEAN_TRUE) - 1
+ };
+
+ SVN_ERR(validate_prop_against_node_kind(propname, path, kind, pool));
+
+ /* This code may place the new prop val in either NEW_VALUE or PROPVAL. */
+ if (!skip_some_checks && (strcmp(propname, SVN_PROP_EOL_STYLE) == 0))
+ {
+ svn_subst_eol_style_t eol_style;
+ const char *ignored_eol;
+ new_value = svn_stringbuf_create_from_string(propval, pool);
+ svn_stringbuf_strip_whitespace(new_value);
+ svn_subst_eol_style_from_value(&eol_style, &ignored_eol, new_value->data);
+ if (eol_style == svn_subst_eol_style_unknown)
+ return svn_error_createf(SVN_ERR_IO_UNKNOWN_EOL, NULL,
+ _("Unrecognized line ending style '%s' for '%s'"),
+ new_value->data,
+ svn_dirent_local_style(path, pool));
+ SVN_ERR(validate_eol_prop_against_file(path, getter, getter_baton,
+ pool));
+ }
+ else if (!skip_some_checks && (strcmp(propname, SVN_PROP_MIME_TYPE) == 0))
+ {
+ new_value = svn_stringbuf_create_from_string(propval, pool);
+ svn_stringbuf_strip_whitespace(new_value);
+ SVN_ERR(svn_mime_type_validate(new_value->data, pool));
+ }
+ else if (strcmp(propname, SVN_PROP_IGNORE) == 0
+ || strcmp(propname, SVN_PROP_EXTERNALS) == 0
+ || strcmp(propname, SVN_PROP_INHERITABLE_IGNORES) == 0
+ || strcmp(propname, SVN_PROP_INHERITABLE_AUTO_PROPS) == 0)
+ {
+ /* Make sure that the last line ends in a newline */
+ if (propval->len == 0
+ || propval->data[propval->len - 1] != '\n')
+ {
+ new_value = svn_stringbuf_create_from_string(propval, pool);
+ svn_stringbuf_appendbyte(new_value, '\n');
+ }
+
+ /* Make sure this is a valid externals property. Do not
+ allow 'skip_some_checks' to override, as there is no circumstance in
+ which this is proper (because there is no circumstance in
+ which Subversion can handle it). */
+ if (strcmp(propname, SVN_PROP_EXTERNALS) == 0)
+ {
+ /* We don't allow "." nor ".." as target directories in
+ an svn:externals line. As it happens, our parse code
+ checks for this, so all we have to is invoke it --
+ we're not interested in the parsed result, only in
+ whether or not the parsing errored. */
+ apr_array_header_t *externals = NULL;
+ apr_array_header_t *duplicate_targets = NULL;
+ SVN_ERR(svn_wc_parse_externals_description3(&externals, path,
+ propval->data, FALSE,
+ /*scratch_*/pool));
+ SVN_ERR(svn_wc__externals_find_target_dups(&duplicate_targets,
+ externals,
+ /*scratch_*/pool,
+ /*scratch_*/pool));
+ if (duplicate_targets && duplicate_targets->nelts > 0)
+ {
+ const char *more_str = "";
+ if (duplicate_targets->nelts > 1)
+ {
+ more_str = apr_psprintf(/*scratch_*/pool,
+ _(" (%d more duplicate targets found)"),
+ duplicate_targets->nelts - 1);
+ }
+ return svn_error_createf(
+ SVN_ERR_WC_DUPLICATE_EXTERNALS_TARGET, NULL,
+ _("Invalid %s property on '%s': "
+ "target '%s' appears more than once%s"),
+ SVN_PROP_EXTERNALS,
+ svn_dirent_local_style(path, pool),
+ APR_ARRAY_IDX(duplicate_targets, 0, const char*),
+ more_str);
+ }
+ }
+ }
+ else if (strcmp(propname, SVN_PROP_KEYWORDS) == 0)
+ {
+ new_value = svn_stringbuf_create_from_string(propval, pool);
+ svn_stringbuf_strip_whitespace(new_value);
+ }
+ else if (svn_prop_is_boolean(propname))
+ {
+ /* SVN_PROP_EXECUTABLE, SVN_PROP_NEEDS_LOCK, SVN_PROP_SPECIAL */
+ propval = &boolean_value;
+ }
+ else if (strcmp(propname, SVN_PROP_MERGEINFO) == 0)
+ {
+ apr_hash_t *mergeinfo;
+ svn_string_t *new_value_str;
+
+ SVN_ERR(svn_mergeinfo_parse(&mergeinfo, propval->data, pool));
+
+ /* Non-inheritable mergeinfo is only valid on directories. */
+ if (kind != svn_node_dir
+ && svn_mergeinfo__is_noninheritable(mergeinfo, pool))
+ return svn_error_createf(
+ SVN_ERR_MERGEINFO_PARSE_ERROR, NULL,
+ _("Cannot set non-inheritable mergeinfo on a non-directory ('%s')"),
+ svn_dirent_local_style(path, pool));
+
+ SVN_ERR(svn_mergeinfo_to_string(&new_value_str, mergeinfo, pool));
+ propval = new_value_str;
+ }
+
+ if (new_value)
+ *propval_p = svn_stringbuf__morph_into_string(new_value);
+ else
+ *propval_p = propval;
+
+ return SVN_NO_ERROR;
+}
+
+
+svn_boolean_t
+svn_wc_is_normal_prop(const char *name)
+{
+ enum svn_prop_kind kind = svn_property_kind2(name);
+ return (kind == svn_prop_regular_kind);
+}
+
+
+svn_boolean_t
+svn_wc_is_wc_prop(const char *name)
+{
+ enum svn_prop_kind kind = svn_property_kind2(name);
+ return (kind == svn_prop_wc_kind);
+}
+
+
+svn_boolean_t
+svn_wc_is_entry_prop(const char *name)
+{
+ enum svn_prop_kind kind = svn_property_kind2(name);
+ return (kind == svn_prop_entry_kind);
+}
+
+
+svn_error_t *
+svn_wc__props_modified(svn_boolean_t *modified_p,
+ svn_wc__db_t *db,
+ const char *local_abspath,
+ apr_pool_t *scratch_pool)
+{
+ SVN_ERR(svn_wc__db_read_info(NULL, NULL, NULL, NULL, NULL, NULL, NULL,
+ NULL, NULL, NULL, NULL, NULL, NULL, NULL,
+ NULL, NULL, NULL, NULL, NULL, NULL, NULL,
+ NULL, NULL, modified_p, NULL, NULL, NULL,
+ db, local_abspath,
+ scratch_pool, scratch_pool));
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_wc_props_modified_p2(svn_boolean_t *modified_p,
+ svn_wc_context_t* wc_ctx,
+ const char *local_abspath,
+ apr_pool_t *scratch_pool)
+{
+ return svn_error_trace(
+ svn_wc__props_modified(modified_p,
+ wc_ctx->db,
+ local_abspath,
+ scratch_pool));
+}
+
+svn_error_t *
+svn_wc__internal_propdiff(apr_array_header_t **propchanges,
+ apr_hash_t **original_props,
+ svn_wc__db_t *db,
+ const char *local_abspath,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ apr_hash_t *baseprops;
+
+ SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath));
+
+ /* ### if pristines are not defined, then should this raise an error,
+ ### or use an empty set? */
+ SVN_ERR(svn_wc__db_read_pristine_props(&baseprops, db, local_abspath,
+ result_pool, scratch_pool));
+
+ if (original_props != NULL)
+ *original_props = baseprops;
+
+ if (propchanges != NULL)
+ {
+ apr_hash_t *actual_props;
+
+ /* Some nodes do not have pristine props, so let's just use an empty
+ set here. Thus, any ACTUAL props are additions. */
+ if (baseprops == NULL)
+ baseprops = apr_hash_make(scratch_pool);
+
+ SVN_ERR(svn_wc__db_read_props(&actual_props, db, local_abspath,
+ result_pool, scratch_pool));
+ /* ### be wary. certain nodes don't have ACTUAL props either. we
+ ### may want to raise an error. or maybe that is a deletion of
+ ### any potential pristine props? */
+
+ SVN_ERR(svn_prop_diffs(propchanges, actual_props, baseprops,
+ result_pool));
+ }
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_wc_get_prop_diffs2(apr_array_header_t **propchanges,
+ apr_hash_t **original_props,
+ svn_wc_context_t *wc_ctx,
+ const char *local_abspath,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ return svn_error_trace(svn_wc__internal_propdiff(propchanges,
+ original_props, wc_ctx->db, local_abspath,
+ result_pool, scratch_pool));
+}
+
+svn_boolean_t
+svn_wc__has_magic_property(const apr_array_header_t *properties)
+{
+ int i;
+
+ for (i = 0; i < properties->nelts; i++)
+ {
+ const svn_prop_t *property = &APR_ARRAY_IDX(properties, i, svn_prop_t);
+
+ if (strcmp(property->name, SVN_PROP_EXECUTABLE) == 0
+ || strcmp(property->name, SVN_PROP_KEYWORDS) == 0
+ || strcmp(property->name, SVN_PROP_EOL_STYLE) == 0
+ || strcmp(property->name, SVN_PROP_SPECIAL) == 0
+ || strcmp(property->name, SVN_PROP_NEEDS_LOCK) == 0)
+ return TRUE;
+ }
+ return FALSE;
+}
+
+svn_error_t *
+svn_wc__get_iprops(apr_array_header_t **inherited_props,
+ svn_wc_context_t *wc_ctx,
+ const char *local_abspath,
+ const char *propname,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ return svn_error_trace(
+ svn_wc__db_read_inherited_props(inherited_props, NULL,
+ wc_ctx->db, local_abspath,
+ propname,
+ result_pool, scratch_pool));
+}
+
+svn_error_t *
+svn_wc__get_cached_iprop_children(apr_hash_t **iprop_paths,
+ svn_depth_t depth,
+ svn_wc_context_t *wc_ctx,
+ const char *local_abspath,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ SVN_ERR(svn_wc__db_get_children_with_cached_iprops(iprop_paths,
+ depth,
+ local_abspath,
+ wc_ctx->db,
+ result_pool,
+ scratch_pool));
+ return SVN_NO_ERROR;
+}
OpenPOWER on IntegriCloud