<commit_before>/*
 * 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 "private/svn_wc_private.h"
#include "private/svn_mergeinfo_private.h"
#include "private/svn_skel.h"
#include "private/svn_string_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;
  const char *native_text;

  SVN_ERR(prop_conflict_from_skel(&conflict_desc, prop_skel, pool, pool));
  native_text = svn_utf_cstring_from_utf8_fuzzy(conflict_desc->data, pool);

  return svn_stream_puts(stream, native_text);
}


/* Get the property reject file for LOCAL_ABSPATH in DB.  Set
   *PREJFILE_ABSPATH to the name of that file, or to NULL if no such
   file is named.  The file may, or may not, exist on disk. */
svn_error_t *
svn_wc__get_prejfile_abspath(const char **prejfile_abspath,
                             svn_wc__db_t *db,
                             const char *local_abspath,
                             apr_pool_t *result_pool,
                             apr_pool_t *scratch_pool)
{
  const apr_array_header_t *conflicts;
  int i;

  SVN_ERR(svn_wc__db_read_conflicts(&conflicts, db, local_abspath,
                                    scratch_pool, scratch_pool));

  for (i = 0; i < conflicts->nelts; i++)
    {
      const svn_wc_conflict_description2_t *cd;
      cd = APR_ARRAY_IDX(conflicts, i, const svn_wc_conflict_description2_t *);

      if (cd->kind == svn_wc_conflict_kind_property)
        {
          *prejfile_abspath = apr_pstrdup(result_pool, cd->their_abspath);
          return SVN_NO_ERROR;
        }
    }

  *prejfile_abspath = NULL;
  return SVN_NO_ERROR;
}

/*---------------------------------------------------------------------*/


/*** 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__perform_props_merge(svn_wc_notify_state_t *state,
                            svn_wc__db_t *db,
                            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 base_merge,
                            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_kind_t kind;
  apr_hash_t *pristine_props = NULL;
  apr_hash_t *actual_props = NULL;
  apr_hash_t *new_pristine_props;
  apr_hash_t *new_actual_props;
  svn_boolean_t had_props, props_mod;
  svn_boolean_t have_base;
  svn_skel_t *work_items = NULL;
  svn_skel_t *conflict_skel = NULL;

  /* 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, NULL, 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));
    }

  /* 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__get_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 = apr_hash_copy(scratch_pool, 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_pristine_props, &new_actual_props,
                              db, local_abspath, kind,
                              baseprops /* server_baseprops */,
                              pristine_props,
                              actual_props,
                              propchanges, base_merge, dry_run,
                              cancel_func, cancel_baton,
                              scratch_pool, scratch_pool));

  if (dry_run)
    {
      return SVN_NO_ERROR;
    }

  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);
    }

  {
    const char *dir_abspath;

    if (kind == svn_kind_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));

    /* After a (not-dry-run) merge, we ALWAYS have props to save.  */
    SVN_ERR_ASSERT(new_pristine_props != NULL && new_actual_props != NULL);

/* See props.h  */
#ifdef SVN__SUPPORT_BASE_MERGE
    if (status == svn_wc__db_status_added)
      SVN_ERR(svn_wc__db_temp_working_set_props(db, local_abspath,
                                                new_base_props, scratch_pool));
    else
      SVN_ERR(svn_wc__db_temp_base_set_props(db, local_abspath,
                                             new_base_props, scratch_pool));
#else
    if (base_merge)
      return svn_error_create(SVN_ERR_UNSUPPORTED_FEATURE, NULL,
                              U_("base_merge=TRUE is no longer supported"));
#endif

    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 (conflict_skel && conflict_func)
    SVN_ERR(svn_wc__conflict_invoke_resolver(db, local_abspath, conflict_skel,
                                             NULL /* merge_options */,
                                             conflict_func, conflict_baton,
                                             scratch_pool));

  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)
{
  return svn_error_trace(svn_wc__perform_props_merge(
                           state,
                           wc_ctx->db,
                           local_abspath,
                           left_version, right_version,
                           baseprops,
                           propchanges,
                           FALSE /* base_merge */,
                           dry_run,
                           conflict_func, conflict_baton,
                           cancel_func, cancel_baton,
                           scratch_pool));
}


/* 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 = "=======";

          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, mine, incoming,
                                                    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;
}

/* Add the property with name PROPNAME to the set of ACTUAL_PROPS on
 * PATH, setting *STATE or *CONFLICT_REMAINS according to merge outcomes.
 *
 * *STATE is an input and output parameter, its value is to be
 * set using set_merge_prop_state().
 *
 * BASE_VAL contains the working copy base property value
 *
 * NEW_VAL contains the value to be set.
 */
static svn_error_t *
apply_single_prop_add(svn_wc_notify_state_t *state,
                      svn_boolean_t *conflict_remains,
                      svn_wc__db_t *db,
                      const char *local_abspath,
                      apr_hash_t *actual_props,
                      const char *propname,
                      const svn_string_t *base_val,
                      const svn_string_t *new_val,
                      apr_pool_t *result_pool,
                      apr_pool_t *scratch_pool)

{
  svn_string_t *working_val
    = apr_hash_get(actual_props, propname, APR_HASH_KEY_STRING);

  *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. */
        set_prop_merge_state(state, svn_wc_notify_state_merged);

      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;
                  apr_hash_set(actual_props, propname,
                               APR_HASH_KEY_STRING, merged_val);
                  set_prop_merge_state(state, svn_wc_notify_state_merged);
                }
            }

          if (!merged_prop)
            *conflict_remains = TRUE;
        }
    }
  else if (base_val)
    *conflict_remains = TRUE;
  else  /* property doesn't yet exist in actual_props...  */
    /* so just set it */
    apr_hash_set(actual_props, propname, APR_HASH_KEY_STRING, new_val);

  return SVN_NO_ERROR;
}


/* Delete the property with name PROPNAME from the set of
 * ACTUAL_PROPS on PATH, setting *STATE or *CONFLICT_REMAINS according to
 * merge outcomes.
 *
 * *STATE is an input and output parameter, its value is to be
 * set using set_merge_prop_state().
 *
 * BASE_VAL contains the working copy base property value
 *
 * OLD_VAL contains the value the of the property the server
 * thinks it's deleting.
 */
static svn_error_t *
apply_single_prop_delete(svn_wc_notify_state_t *state,
                         svn_boolean_t *conflict_remains,
                         svn_wc__db_t *db,
                         const char *local_abspath,
                         apr_hash_t *actual_props,
                         const char *propname,
                         const svn_string_t *base_val,
                         const svn_string_t *old_val,
                         apr_pool_t *result_pool,
                         apr_pool_t *scratch_pool)
{
  svn_string_t *working_val
    = apr_hash_get(actual_props, propname, APR_HASH_KEY_STRING);

  *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
        {
          apr_hash_set(actual_props, propname, APR_HASH_KEY_STRING, NULL);
          if (old_val)
            /* This is a merge, merging a delete into non-existent
               property or a local addition of same prop value. */
            set_prop_merge_state(state, svn_wc_notify_state_merged);
        }
    }

  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 */
             apr_hash_set(actual_props, propname, APR_HASH_KEY_STRING, NULL);
           else
             *conflict_remains = TRUE;
         }
       else
         /* The property is locally deleted from the same value, so it's
            a merge */
         set_prop_merge_state(state, svn_wc_notify_state_merged);
    }

  else
    *conflict_remains = TRUE;

  return SVN_NO_ERROR;
}


/* Merge a change to the mergeinfo property. The same as
   apply_single_prop_change(), except that the PROPNAME 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(svn_wc_notify_state_t *state,
                                   svn_boolean_t *conflict_remains,
                                   svn_wc__db_t *db,
                                   const char *local_abspath,
                                   apr_hash_t *actual_props,
                                   const char *propname,
                                   const svn_string_t *base_val,
                                   const svn_string_t *old_val,
                                   const svn_string_t *new_val,
                                   apr_pool_t *result_pool,
                                   apr_pool_t *scratch_pool)
{
  svn_string_t *working_val
    = apr_hash_get(actual_props, propname, APR_HASH_KEY_STRING);

  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 */
            set_prop_merge_state(state, svn_wc_notify_state_merged);
          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));
                  apr_hash_set(actual_props, propname,
                               APR_HASH_KEY_STRING, new_val);
                  set_prop_merge_state(state, svn_wc_notify_state_merged);
            }
        }

      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));
          apr_hash_set(actual_props, propname, APR_HASH_KEY_STRING,
                       mergeinfo_string);
    }

  else /* means working && base && svn_string_compare(working, base) */
    {
      if (svn_string_compare(old_val, base_val))
        apr_hash_set(actual_props, propname, APR_HASH_KEY_STRING, 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));
              apr_hash_set(actual_props, propname,
                           APR_HASH_KEY_STRING, new_val);
              set_prop_merge_state(state, svn_wc_notify_state_merged);
        }
    }

  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(svn_wc_notify_state_t *state,
                                 svn_boolean_t *conflict_remains,
                                 svn_wc__db_t *db,
                                 const char *local_abspath,
                                 apr_hash_t *actual_props,
                                 const char *propname,
                                 const svn_string_t *base_val,
                                 const svn_string_t *old_val,
                                 const svn_string_t *new_val,
                                 apr_pool_t *result_pool,
                                 apr_pool_t *scratch_pool)
{
  svn_string_t *working_val
    = apr_hash_get(actual_props, propname, APR_HASH_KEY_STRING);

  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))
        set_prop_merge_state(state, svn_wc_notify_state_merged);
    }
  /* 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. */
      apr_hash_set(actual_props, propname, APR_HASH_KEY_STRING, new_val);
    }
  else
    {
      /* Merge the change. */
      *conflict_remains = TRUE;
    }

  return SVN_NO_ERROR;
}

/* Change the property with name PROPNAME in the set of ACTUAL_PROPS
 * on PATH, setting *STATE or *CONFLICT_REMAINS according to the merge outcome.
 *
 * *STATE is an input and output parameter, its value is to be
 * set using set_prop_merge_state(). (May be null.).
 *
 * 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.)
 */
static svn_error_t *
apply_single_prop_change(svn_wc_notify_state_t *state,
                         svn_boolean_t *conflict_remains,
                         svn_wc__db_t *db,
                         const char *local_abspath,
                         apr_hash_t *actual_props,
                         const char *propname,
                         const svn_string_t *base_val,
                         const svn_string_t *old_val,
                         const svn_string_t *new_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(state,
                                                            conflict_remains,
                                                            db, local_abspath,
                                                            actual_props,
                                                            propname,
                                                            base_val,
                                                            old_val,
                                                            new_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(state, conflict_remains,
                                               db, local_abspath,
                                               actual_props,
                                               propname, base_val, old_val,
                                               new_val,
                                               result_pool, scratch_pool));
    }

  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_pristine_props,
                    apr_hash_t **new_actual_props,
                    svn_wc__db_t *db,
                    const char *local_abspath,
                    svn_kind_t kind,
                    apr_hash_t *server_baseprops,
                    apr_hash_t *pristine_props,
                    apr_hash_t *actual_props,
                    const apr_array_header_t *propchanges,
                    svn_boolean_t base_merge,
                    svn_boolean_t dry_run,
                    svn_cancel_func_t cancel_func,
                    void *cancel_baton,
                    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 *old_actual_props;
  apr_hash_t *their_props;

  SVN_ERR_ASSERT(pristine_props != NULL);
  SVN_ERR_ASSERT(actual_props != NULL);

  /* Just copy the pointers as we copy the data in the skel if
     necessary */
  old_actual_props = apr_hash_copy(scratch_pool, actual_props);

  *new_pristine_props = NULL;
  *new_actual_props = NULL;

  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 char *propname;
      svn_boolean_t conflict_remains;
      const svn_prop_t *incoming_change;
      const svn_string_t *from_val, *to_val, *base_val;
      const svn_string_t *mine_val;

      svn_pool_clear(iterpool);

      /* Should we stop the prop merging process?  */
      if (cancel_func)
        SVN_ERR(cancel_func(cancel_baton));

      /* For the incoming propchange, figure out the TO and FROM values. */
      incoming_change = &APR_ARRAY_IDX(propchanges, i, svn_prop_t);
      propname = incoming_change->name;
      to_val = incoming_change->value
        ? svn_string_dup(incoming_change->value, result_pool) : NULL;
      from_val = apr_hash_get(server_baseprops, propname, APR_HASH_KEY_STRING);

      base_val = apr_hash_get(pristine_props, propname, APR_HASH_KEY_STRING);

      if (base_merge)
        apr_hash_set(pristine_props, propname, APR_HASH_KEY_STRING, to_val);

      /* Save MINE for later message generation.  */
      mine_val = apr_hash_get(actual_props, propname, APR_HASH_KEY_STRING);

      apr_hash_set(their_props, propname, APR_HASH_KEY_STRING, 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);

      if (! from_val)  /* adding a new property */
        SVN_ERR(apply_single_prop_add(state, &conflict_remains,
                                      db, local_abspath,
                                      actual_props,
                                      propname, base_val, to_val,
                                      result_pool, iterpool));

      else if (! to_val) /* delete an existing property */
        SVN_ERR(apply_single_prop_delete(state, &conflict_remains,
                                         db, local_abspath,
                                         actual_props,
                                         propname, base_val, from_val,
                                         result_pool, iterpool));

      else  /* changing an existing property */
        SVN_ERR(apply_single_prop_change(state, &conflict_remains,
                                         db, local_abspath,
                                         actual_props,
                                         propname, base_val, from_val, to_val,
                                         result_pool, iterpool));


      /* merging logic complete, now we need to possibly log conflict<commit_msg>* subversion/libsvn_wc/props.c (svn_wc__merge_props): Remove unused variable.<commit_after>/*
 * 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 "private/svn_wc_private.h"
#include "private/svn_mergeinfo_private.h"
#include "private/svn_skel.h"
#include "private/svn_string_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;
  const char *native_text;

  SVN_ERR(prop_conflict_from_skel(&conflict_desc, prop_skel, pool, pool));
  native_text = svn_utf_cstring_from_utf8_fuzzy(conflict_desc->data, pool);

  return svn_stream_puts(stream, native_text);
}


/* Get the property reject file for LOCAL_ABSPATH in DB.  Set
   *PREJFILE_ABSPATH to the name of that file, or to NULL if no such
   file is named.  The file may, or may not, exist on disk. */
svn_error_t *
svn_wc__get_prejfile_abspath(const char **prejfile_abspath,
                             svn_wc__db_t *db,
                             const char *local_abspath,
                             apr_pool_t *result_pool,
                             apr_pool_t *scratch_pool)
{
  const apr_array_header_t *conflicts;
  int i;

  SVN_ERR(svn_wc__db_read_conflicts(&conflicts, db, local_abspath,
                                    scratch_pool, scratch_pool));

  for (i = 0; i < conflicts->nelts; i++)
    {
      const svn_wc_conflict_description2_t *cd;
      cd = APR_ARRAY_IDX(conflicts, i, const svn_wc_conflict_description2_t *);

      if (cd->kind == svn_wc_conflict_kind_property)
        {
          *prejfile_abspath = apr_pstrdup(result_pool, cd->their_abspath);
          return SVN_NO_ERROR;
        }
    }

  *prejfile_abspath = NULL;
  return SVN_NO_ERROR;
}

/*---------------------------------------------------------------------*/


/*** 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__perform_props_merge(svn_wc_notify_state_t *state,
                            svn_wc__db_t *db,
                            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 base_merge,
                            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_kind_t kind;
  apr_hash_t *pristine_props = NULL;
  apr_hash_t *actual_props = NULL;
  apr_hash_t *new_pristine_props;
  apr_hash_t *new_actual_props;
  svn_boolean_t had_props, props_mod;
  svn_boolean_t have_base;
  svn_skel_t *work_items = NULL;
  svn_skel_t *conflict_skel = NULL;

  /* 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, NULL, 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));
    }

  /* 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__get_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 = apr_hash_copy(scratch_pool, 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_pristine_props, &new_actual_props,
                              db, local_abspath, kind,
                              baseprops /* server_baseprops */,
                              pristine_props,
                              actual_props,
                              propchanges, base_merge, dry_run,
                              cancel_func, cancel_baton,
                              scratch_pool, scratch_pool));

  if (dry_run)
    {
      return SVN_NO_ERROR;
    }

  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);
    }

  {
    const char *dir_abspath;

    if (kind == svn_kind_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));

    /* After a (not-dry-run) merge, we ALWAYS have props to save.  */
    SVN_ERR_ASSERT(new_pristine_props != NULL && new_actual_props != NULL);

/* See props.h  */
#ifdef SVN__SUPPORT_BASE_MERGE
    if (status == svn_wc__db_status_added)
      SVN_ERR(svn_wc__db_temp_working_set_props(db, local_abspath,
                                                new_base_props, scratch_pool));
    else
      SVN_ERR(svn_wc__db_temp_base_set_props(db, local_abspath,
                                             new_base_props, scratch_pool));
#else
    if (base_merge)
      return svn_error_create(SVN_ERR_UNSUPPORTED_FEATURE, NULL,
                              U_("base_merge=TRUE is no longer supported"));
#endif

    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 (conflict_skel && conflict_func)
    SVN_ERR(svn_wc__conflict_invoke_resolver(db, local_abspath, conflict_skel,
                                             NULL /* merge_options */,
                                             conflict_func, conflict_baton,
                                             scratch_pool));

  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)
{
  return svn_error_trace(svn_wc__perform_props_merge(
                           state,
                           wc_ctx->db,
                           local_abspath,
                           left_version, right_version,
                           baseprops,
                           propchanges,
                           FALSE /* base_merge */,
                           dry_run,
                           conflict_func, conflict_baton,
                           cancel_func, cancel_baton,
                           scratch_pool));
}


/* 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 = "=======";

          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, mine, incoming,
                                                    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;
}

/* Add the property with name PROPNAME to the set of ACTUAL_PROPS on
 * PATH, setting *STATE or *CONFLICT_REMAINS according to merge outcomes.
 *
 * *STATE is an input and output parameter, its value is to be
 * set using set_merge_prop_state().
 *
 * BASE_VAL contains the working copy base property value
 *
 * NEW_VAL contains the value to be set.
 */
static svn_error_t *
apply_single_prop_add(svn_wc_notify_state_t *state,
                      svn_boolean_t *conflict_remains,
                      svn_wc__db_t *db,
                      const char *local_abspath,
                      apr_hash_t *actual_props,
                      const char *propname,
                      const svn_string_t *base_val,
                      const svn_string_t *new_val,
                      apr_pool_t *result_pool,
                      apr_pool_t *scratch_pool)

{
  svn_string_t *working_val
    = apr_hash_get(actual_props, propname, APR_HASH_KEY_STRING);

  *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. */
        set_prop_merge_state(state, svn_wc_notify_state_merged);

      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;
                  apr_hash_set(actual_props, propname,
                               APR_HASH_KEY_STRING, merged_val);
                  set_prop_merge_state(state, svn_wc_notify_state_merged);
                }
            }

          if (!merged_prop)
            *conflict_remains = TRUE;
        }
    }
  else if (base_val)
    *conflict_remains = TRUE;
  else  /* property doesn't yet exist in actual_props...  */
    /* so just set it */
    apr_hash_set(actual_props, propname, APR_HASH_KEY_STRING, new_val);

  return SVN_NO_ERROR;
}


/* Delete the property with name PROPNAME from the set of
 * ACTUAL_PROPS on PATH, setting *STATE or *CONFLICT_REMAINS according to
 * merge outcomes.
 *
 * *STATE is an input and output parameter, its value is to be
 * set using set_merge_prop_state().
 *
 * BASE_VAL contains the working copy base property value
 *
 * OLD_VAL contains the value the of the property the server
 * thinks it's deleting.
 */
static svn_error_t *
apply_single_prop_delete(svn_wc_notify_state_t *state,
                         svn_boolean_t *conflict_remains,
                         svn_wc__db_t *db,
                         const char *local_abspath,
                         apr_hash_t *actual_props,
                         const char *propname,
                         const svn_string_t *base_val,
                         const svn_string_t *old_val,
                         apr_pool_t *result_pool,
                         apr_pool_t *scratch_pool)
{
  svn_string_t *working_val
    = apr_hash_get(actual_props, propname, APR_HASH_KEY_STRING);

  *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
        {
          apr_hash_set(actual_props, propname, APR_HASH_KEY_STRING, NULL);
          if (old_val)
            /* This is a merge, merging a delete into non-existent
               property or a local addition of same prop value. */
            set_prop_merge_state(state, svn_wc_notify_state_merged);
        }
    }

  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 */
             apr_hash_set(actual_props, propname, APR_HASH_KEY_STRING, NULL);
           else
             *conflict_remains = TRUE;
         }
       else
         /* The property is locally deleted from the same value, so it's
            a merge */
         set_prop_merge_state(state, svn_wc_notify_state_merged);
    }

  else
    *conflict_remains = TRUE;

  return SVN_NO_ERROR;
}


/* Merge a change to the mergeinfo property. The same as
   apply_single_prop_change(), except that the PROPNAME 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(svn_wc_notify_state_t *state,
                                   svn_boolean_t *conflict_remains,
                                   svn_wc__db_t *db,
                                   const char *local_abspath,
                                   apr_hash_t *actual_props,
                                   const char *propname,
                                   const svn_string_t *base_val,
                                   const svn_string_t *old_val,
                                   const svn_string_t *new_val,
                                   apr_pool_t *result_pool,
                                   apr_pool_t *scratch_pool)
{
  svn_string_t *working_val
    = apr_hash_get(actual_props, propname, APR_HASH_KEY_STRING);

  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 */
            set_prop_merge_state(state, svn_wc_notify_state_merged);
          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));
                  apr_hash_set(actual_props, propname,
                               APR_HASH_KEY_STRING, new_val);
                  set_prop_merge_state(state, svn_wc_notify_state_merged);
            }
        }

      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));
          apr_hash_set(actual_props, propname, APR_HASH_KEY_STRING,
                       mergeinfo_string);
    }

  else /* means working && base && svn_string_compare(working, base) */
    {
      if (svn_string_compare(old_val, base_val))
        apr_hash_set(actual_props, propname, APR_HASH_KEY_STRING, 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));
              apr_hash_set(actual_props, propname,
                           APR_HASH_KEY_STRING, new_val);
              set_prop_merge_state(state, svn_wc_notify_state_merged);
        }
    }

  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(svn_wc_notify_state_t *state,
                                 svn_boolean_t *conflict_remains,
                                 svn_wc__db_t *db,
                                 const char *local_abspath,
                                 apr_hash_t *actual_props,
                                 const char *propname,
                                 const svn_string_t *base_val,
                                 const svn_string_t *old_val,
                                 const svn_string_t *new_val,
                                 apr_pool_t *result_pool,
                                 apr_pool_t *scratch_pool)
{
  svn_string_t *working_val
    = apr_hash_get(actual_props, propname, APR_HASH_KEY_STRING);

  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))
        set_prop_merge_state(state, svn_wc_notify_state_merged);
    }
  /* 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. */
      apr_hash_set(actual_props, propname, APR_HASH_KEY_STRING, new_val);
    }
  else
    {
      /* Merge the change. */
      *conflict_remains = TRUE;
    }

  return SVN_NO_ERROR;
}

/* Change the property with name PROPNAME in the set of ACTUAL_PROPS
 * on PATH, setting *STATE or *CONFLICT_REMAINS according to the merge outcome.
 *
 * *STATE is an input and output parameter, its value is to be
 * set using set_prop_merge_state(). (May be null.).
 *
 * 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.)
 */
static svn_error_t *
apply_single_prop_change(svn_wc_notify_state_t *state,
                         svn_boolean_t *conflict_remains,
                         svn_wc__db_t *db,
                         const char *local_abspath,
                         apr_hash_t *actual_props,
                         const char *propname,
                         const svn_string_t *base_val,
                         const svn_string_t *old_val,
                         const svn_string_t *new_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(state,
                                                            conflict_remains,
                                                            db, local_abspath,
                                                            actual_props,
                                                            propname,
                                                            base_val,
                                                            old_val,
                                                            new_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(state, conflict_remains,
                                               db, local_abspath,
                                               actual_props,
                                               propname, base_val, old_val,
                                               new_val,
                                               result_pool, scratch_pool));
    }

  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_pristine_props,
                    apr_hash_t **new_actual_props,
                    svn_wc__db_t *db,
                    const char *local_abspath,
                    svn_kind_t kind,
                    apr_hash_t *server_baseprops,
                    apr_hash_t *pristine_props,
                    apr_hash_t *actual_props,
                    const apr_array_header_t *propchanges,
                    svn_boolean_t base_merge,
                    svn_boolean_t dry_run,
                    svn_cancel_func_t cancel_func,
                    void *cancel_baton,
                    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 *old_actual_props;
  apr_hash_t *their_props;

  SVN_ERR_ASSERT(pristine_props != NULL);
  SVN_ERR_ASSERT(actual_props != NULL);

  /* Just copy the pointers as we copy the data in the skel if
     necessary */
  old_actual_props = apr_hash_copy(scratch_pool, actual_props);

  *new_pristine_props = NULL;
  *new_actual_props = NULL;

  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 char *propname;
      svn_boolean_t conflict_remains;
      const svn_prop_t *incoming_change;
      const svn_string_t *from_val, *to_val, *base_val;

      svn_pool_clear(iterpool);

      /* Should we stop the prop merging process?  */
      if (cancel_func)
        SVN_ERR(cancel_func(cancel_baton));

      /* For the incoming propchange, figure out the TO and FROM values. */
      incoming_change = &APR_ARRAY_IDX(propchanges, i, svn_prop_t);
      propname = incoming_change->name;
      to_val = incoming_change->value
        ? svn_string_dup(incoming_change->value, result_pool) : NULL;
      from_val = apr_hash_get(server_baseprops, propname, APR_HASH_KEY_STRING);

      base_val = apr_hash_get(pristine_props, propname, APR_HASH_KEY_STRING);

      if (base_merge)
        apr_hash_set(pristine_props, propname, APR_HASH_KEY_STRING, to_val);

      apr_hash_set(their_props, propname, APR_HASH_KEY_STRING, 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);

      if (! from_val)  /* adding a new property */
        SVN_ERR(apply_single_prop_add(state, &conflict_remains,
                                      db, local_abspath,
                                      actual_props,
                                      propname, base_val, to_val,
                                      result_pool, iterpool));

      else if (! to_val) /* delete an existing property */
        SVN_ERR(apply_single_prop_delete(state, &conflict_remains,
                                         db, local_abspath,
                                         actual_props,
                                         propname, base_val, from_val,
                                         result_pool, iterpool));

      else  /* changing an existing property */
        SVN_ERR(apply_single_prop_change(state, &conflict_remains,
                                         db, local_abspath,
                                         actual_props,
                                         propname, base_val, from_val, to_val,
                                         result_pool, iterpool));


      /* merging logic complete, now we need to possibly log conflict
{'fraction_non_alphanumeric': 0.0749702698889404, 'fraction_numerical': 0.0009876443674037047, 'mean_word_length': 1.7337521007245777, 'pattern_counts': {'":': 0, '<': 49, '<?xml version=': 0, '>': 163, 'https://': 0, 'lorem ipsum': 0, 'www.': 2, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 412, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '942917', 'n_tokens_mistral': 28909, 'n_tokens_neox': 28241, 'n_words': 6899}