<commit_before>/*
 * merge.c:  merging changes into a working file
 *
 * ====================================================================
 *    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 "svn_wc.h"
#include "svn_diff.h"
#include "svn_dirent_uri.h"
#include "svn_path.h"
#include "svn_pools.h"

#include "wc.h"
#include "entries.h"
#include "adm_files.h"
#include "translate.h"
#include "log.h"
#include "lock.h"
#include "workqueue.h"

#include "svn_private_config.h"



/* Return a pointer to the svn_prop_t structure from PROP_DIFF
   belonging to PROP_NAME, if any.  NULL otherwise.*/
static const svn_prop_t *
get_prop(const apr_array_header_t *prop_diff,
         const char *prop_name)
{
  if (prop_diff)
    {
      int i;
      for (i = 0; i < prop_diff->nelts; i++)
        {
          const svn_prop_t *elt = &APR_ARRAY_IDX(prop_diff, i, svn_prop_t);
          if (strcmp(elt->name,prop_name) == 0)
            return elt;
        }
    }

  return NULL;
}


/* Detranslate a working copy file MERGE_TARGET to achieve the effect of:

   1. Detranslate
   2. Install new props
   3. Retranslate
   4. Detranslate

   in 1 pass to get a file which can be compared with the left and right
   files which were created with the 'new props' above.

   Property changes make this a little complex though. Changes in

   - svn:mime-type
   - svn:eol-style
   - svn:keywords
   - svn:special

   may change the way a file is translated.

   Effect for svn:mime-type:

     The value for svn:mime-type affects the translation wrt keywords
     and eol-style settings.

   I) both old and new mime-types are texty
      -> just do the translation dance (as lined out below)

   II) the old one is texty, the new one is binary
      -> detranslate with the old eol-style and keywords
         (the new re+detranslation is a no-op)

   III) the old one is binary, the new one texty
      -> detranslate with the new eol-style
         (the old detranslation is a no-op)

   IV) the old and new ones are binary
      -> don't detranslate, just make a straight copy


   Effect for svn:eol-style

   I) On add or change use the new value

   II) otherwise: use the old value (absent means 'no translation')


   Effect for svn:keywords

     Always use old settings (re+detranslation are no-op)


   Effect for svn:special

     Always use the old settings (same reasons as for svn:keywords)

*/
static svn_error_t *
detranslate_wc_file(const char **detranslated_abspath,
                    svn_wc__db_t *db,
                    const char *target_abspath,
                    svn_boolean_t force_copy,
                    const apr_array_header_t *prop_diff,
                    const char *source_abspath,
                    apr_pool_t *result_pool,
                    apr_pool_t *scratch_pool)
{
  svn_boolean_t is_binary;
  const svn_prop_t *prop;
  svn_subst_eol_style_t style;
  const char *eol;
  apr_hash_t *keywords;
  svn_boolean_t special;

  /* Decide if the merge target currently is a text or binary file. */
  SVN_ERR(svn_wc__marked_as_binary(&is_binary, target_abspath, db,
                                   scratch_pool));

  /* See if we need to do a straight copy:
     - old and new mime-types are binary, or
     - old mime-type is binary and no new mime-type specified */
  if (is_binary
      && (((prop = get_prop(prop_diff, SVN_PROP_MIME_TYPE))
           && prop->value && svn_mime_type_is_binary(prop->value->data))
          || prop == NULL))
    {
      /* this is case IV above */
      keywords = NULL;
      special = FALSE;
      eol = NULL;
      style = svn_subst_eol_style_none;
    }
  else if ((!is_binary)
           && (prop = get_prop(prop_diff, SVN_PROP_MIME_TYPE))
           && prop->value && svn_mime_type_is_binary(prop->value->data))
    {
      /* Old props indicate texty, new props indicate binary:
         detranslate keywords and old eol-style */
      SVN_ERR(svn_wc__get_keywords(&keywords, db, target_abspath, NULL,
                                   scratch_pool, scratch_pool));
      SVN_ERR(svn_wc__get_special(&special, db, target_abspath, scratch_pool));
    }
  else
    {
      /* New props indicate texty, regardless of old props */

      /* In case the file used to be special, detranslate specially */
      SVN_ERR(svn_wc__get_special(&special, db, target_abspath, scratch_pool));

      if (special)
        {
          keywords = NULL;
          eol = NULL;
          style = svn_subst_eol_style_none;
        }
      else
        {
          /* In case a new eol style was set, use that for detranslation */
          if ((prop = get_prop(prop_diff, SVN_PROP_EOL_STYLE)) && prop->value)
            {
              /* Value added or changed */
              svn_subst_eol_style_from_value(&style, &eol, prop->value->data);
            }
          else if (!is_binary)
            SVN_ERR(svn_wc__get_eol_style(&style, &eol, db, target_abspath,
                                          scratch_pool, scratch_pool));
          else
            {
              eol = NULL;
              style = svn_subst_eol_style_none;
            }

          /* In case there were keywords, detranslate with keywords
             (iff we were texty) */
          if (!is_binary)
            SVN_ERR(svn_wc__get_keywords(&keywords, db, target_abspath, NULL,
                                         scratch_pool, scratch_pool));
          else
            keywords = NULL;
        }
    }

  /* Now, detranslate with the settings we created above */

  if (force_copy || keywords || eol || special)
    {
      const char *detranslated;

      /* Force a copy into the temporary wc area to avoid having
         temporary files created below to appear in the actual wc. */

      /* ### svn_subst_copy_and_translate3() also creates a tempfile
         ### internally.  Anyway to piggyback on that? */
      SVN_ERR(svn_io_open_unique_file3(NULL, &detranslated, NULL,
                                      svn_io_file_del_on_pool_cleanup,
                                      result_pool, scratch_pool));

      /* Always 'repair' EOLs here, so that we can apply a diff that
         changes from inconsistent newlines and no 'svn:eol-style' to
         consistent newlines and 'svn:eol-style' set.  */

      if (style == svn_subst_eol_style_native)
        eol = SVN_SUBST_NATIVE_EOL_STR;
      else if (style != svn_subst_eol_style_fixed
               && style != svn_subst_eol_style_none)
        return svn_error_create(SVN_ERR_IO_UNKNOWN_EOL, NULL, NULL);

      SVN_ERR(svn_subst_copy_and_translate3(source_abspath,
                                            detranslated,
                                            eol,
                                            TRUE /* repair */,
                                            keywords,
                                            FALSE /* contract keywords */,
                                            special,
                                            scratch_pool));

      SVN_ERR(svn_dirent_get_absolute(detranslated_abspath, detranslated,
                                      result_pool));
    }
  else
    *detranslated_abspath = apr_pstrdup(result_pool, source_abspath);

  return SVN_NO_ERROR;
}

/* Updates (by copying and translating) the eol style in
   OLD_TARGET returning the filename containing the
   correct eol style in NEW_TARGET, if an eol style
   change is contained in PROP_DIFF */
static svn_error_t *
maybe_update_target_eols(const char **new_target_abspath,
                         svn_wc__db_t *db,
                         const char *old_target_abspath,
                         const apr_array_header_t *prop_diff,
                         apr_pool_t *result_pool,
                         apr_pool_t *scratch_pool)
{
  const svn_prop_t *prop = get_prop(prop_diff, SVN_PROP_EOL_STYLE);

  if (prop && prop->value)
    {
      const char *eol;
      const char *tmp_new;

      svn_subst_eol_style_from_value(NULL, &eol, prop->value->data);
      SVN_ERR(svn_io_open_unique_file3(NULL, &tmp_new, NULL,
                                       svn_io_file_del_on_pool_cleanup,
                                       result_pool, scratch_pool));

      /* Always 'repair' EOLs here, so that we can apply a diff that
         changes from inconsistent newlines and no 'svn:eol-style' to
         consistent newlines and 'svn:eol-style' set.  */
      SVN_ERR(svn_subst_copy_and_translate3(old_target_abspath,
                                            tmp_new,
                                            eol, TRUE /* repair EOLs */,
                                            NULL, FALSE,
                                            FALSE, scratch_pool));
      *new_target_abspath = apr_pstrdup(result_pool, tmp_new);
    }
  else
    *new_target_abspath = apr_pstrdup(result_pool, old_target_abspath);

  return SVN_NO_ERROR;
}


/* Helper for do_text_merge_internal() below. */
static void
init_conflict_markers(const char **target_marker,
                      const char **left_marker,
                      const char **right_marker,
                      const char *target_label,
                      const char *left_label,
                      const char *right_label,
                      apr_pool_t *pool)
{
  /* Labels fall back to sensible defaults if not specified. */
  if (target_label)
    *target_marker = apr_psprintf(pool, "<<<<<<< %s", target_label);
  else
    *target_marker = "<<<<<<< .working";

  if (left_label)
    *left_marker = apr_psprintf(pool, "||||||| %s", left_label);
  else
    *left_marker = "||||||| .old";

  if (right_label)
    *right_marker = apr_psprintf(pool, ">>>>>>> %s", right_label);
  else
    *right_marker = ">>>>>>> .new";
}

/* Do a 3-way merge of the files at paths LEFT, DETRANSLATED_TARGET,
 * and RIGHT, using diff options provided in OPTIONS.  Store the merge
 * result in the file RESULT_F.
 * If there are conflicts, set *CONTAINS_CONFLICTS to true, and use
 * TARGET_LABEL, LEFT_LABEL, and RIGHT_LABEL as labels for conflict
 * markers.  Else, set *CONTAINS_CONFLICTS to false.
 * Do all allocations in POOL. */
static svn_error_t*
do_text_merge(svn_boolean_t *contains_conflicts,
              apr_file_t *result_f,
              const char *detranslated_target,
              const char *left,
              const char *right,
              const char *target_label,
              const char *left_label,
              const char *right_label,
              svn_diff_file_options_t *options,
              apr_pool_t *pool)
{
  svn_diff_t *diff;
  svn_stream_t *ostream;
  const char *target_marker;
  const char *left_marker;
  const char *right_marker;

  init_conflict_markers(&target_marker, &left_marker, &right_marker,
                        target_label, left_label, right_label, pool);

  SVN_ERR(svn_diff_file_diff3_2(&diff, left, detranslated_target, right,
                                options, pool));

  ostream = svn_stream_from_aprfile2(result_f, TRUE, pool);

  SVN_ERR(svn_diff_file_output_merge2(ostream, diff,
                                      left, detranslated_target, right,
                                      left_marker,
                                      target_marker,
                                      right_marker,
                                      "=======", /* separator */
                                      svn_diff_conflict_display_modified_latest,
                                      pool));
  SVN_ERR(svn_stream_close(ostream));

  *contains_conflicts = svn_diff_contains_conflicts(diff);

  return SVN_NO_ERROR;
}

/* Same as do_text_merge() above, but use the external diff3
 * command DIFF3_CMD to perform the merge.  Pass MERGE_OPTIONS
 * to the diff3 command.  Do all allocations in POOL. */
static svn_error_t*
do_text_merge_external(svn_boolean_t *contains_conflicts,
                            apr_file_t *result_f,
                            const char *detranslated_target,
                            const char *left_abspath,
                            const char *right_abspath,
                            const char *target_label,
                            const char *left_label,
                            const char *right_label,
                            const char *diff3_cmd,
                            const apr_array_header_t *merge_options,
                            apr_pool_t *pool)
{
  int exit_code;

  SVN_ERR(svn_io_run_diff3_2(&exit_code, ".",
                             detranslated_target, left_abspath, right_abspath,
                             target_label, left_label, right_label,
                             result_f, diff3_cmd,
                             merge_options, pool));

  *contains_conflicts = exit_code == 1;

  return SVN_NO_ERROR;
}

/* Loggy-copy the merge result obtained during interactive conflict
 * resolution to the file RESULT_TARGET. The merge result is expected
 * in the same directory as TARGET_ABSPATH the same basename as
 * TARGET_ABSPATH, but followed by ".edited".
 * Use LOG_ACCUM as log accumulator.  DB contains an access baton with
 * a write lock for the directory containing RESULT_TARGET.
 * Do all allocations in POOL.
 */
static svn_error_t*
save_merge_result(svn_wc__db_t *db,
                  const char *target_abspath,
                  const char *result_target,
                  apr_pool_t *pool)
{
  const char *edited_copy;
  const char *merge_dirpath;
  const char *merge_filename;

  svn_dirent_split(target_abspath, &merge_dirpath, &merge_filename, pool);

  /* ### Should use preserved-conflict-file-exts. */
  SVN_ERR(svn_io_open_uniquely_named(NULL,
                                     &edited_copy,
                                     merge_dirpath,
                                     merge_filename,
                                     ".edited",
                                     svn_io_file_del_none,
                                     pool, pool));
  SVN_ERR(svn_wc__loggy_copy(db, merge_dirpath,
                             result_target, edited_copy, pool));

  return SVN_NO_ERROR;
}

/* Deal with the RESULT of the conflict resolution callback.
 * LEFT, RIGHT, and MERGE_TARGET are the files involved in
 * the 3-way merge.  Store the result of the 3-way merge in
 * MERGE_OUTCOME.  If the callback did not provide the name to
 * a merged file, use RESULT_TARGET is a fallback.
 * DETRANSLATED_TARGET is the detranslated version of MERGE_TARGET
 * (see detranslate_wc_file() above).  OPTIONS are passed to the
 * diff3 implementation in case a 3-way  merge has to be carried out.
 * Do all allocations in POOL. */
static svn_error_t*
eval_conflict_func_result(enum svn_wc_merge_outcome_t *merge_outcome,
                          svn_wc_conflict_result_t *result,
                          svn_wc__db_t *db,
                          const char *left_abspath,
                          const char *right_abspath,
                          const char *target_abspath,
                          const char *copyfrom_text,
                          const char *adm_abspath,
                          const char *result_target,
                          const char *detranslated_target,
                          svn_diff_file_options_t *options,
                          apr_pool_t *pool)
{
  switch (result->choice)
    {
      /* If the callback wants to use one of the fulltexts
         to resolve the conflict, so be it.*/
      case svn_wc_conflict_choose_base:
        {
          SVN_ERR(svn_wc__loggy_copy(db, adm_abspath,
                                     left_abspath, target_abspath,
                                     pool));
          *merge_outcome = svn_wc_merge_merged;
          return SVN_NO_ERROR;
        }
      case svn_wc_conflict_choose_theirs_full:
        {
          SVN_ERR(svn_wc__loggy_copy(db, adm_abspath,
                                     right_abspath, target_abspath,
                                     pool));
          *merge_outcome = svn_wc_merge_merged;
          return SVN_NO_ERROR;
        }
      case svn_wc_conflict_choose_mine_full:
        {
          /* Do nothing to merge_target, let it live untouched! */
          *merge_outcome = svn_wc_merge_merged;
          return SVN_NO_ERROR;
        }
      case svn_wc_conflict_choose_theirs_conflict:
      case svn_wc_conflict_choose_mine_conflict:
        {
          const char *chosen_path;
          const char *temp_dir;
          svn_stream_t *chosen_stream;
          svn_diff_t *diff;
          svn_diff_conflict_display_style_t style;

          style = result->choice == svn_wc_conflict_choose_theirs_conflict
                    ? svn_diff_conflict_display_latest
                    : svn_diff_conflict_display_modified;

          SVN_ERR(svn_wc__db_temp_wcroot_tempdir(&temp_dir, db, adm_abspath,
                                                 pool, pool));
          SVN_ERR(svn_stream_open_unique(&chosen_stream, &chosen_path,
                                         temp_dir,
                                         svn_io_file_del_none, pool, pool));

          SVN_ERR(svn_diff_file_diff3_2(&diff,
                                        left_abspath,
                                        detranslated_target, right_abspath,
                                        options, pool));
          SVN_ERR(svn_diff_file_output_merge2(chosen_stream, diff,
                                              left_abspath,
                                              detranslated_target,
                                              right_abspath,
                                              /* markers ignored */
                                              NULL, NULL,
                                              NULL, NULL,
                                              style,
                                              pool));
          SVN_ERR(svn_stream_close(chosen_stream));
          SVN_ERR(svn_wc__loggy_copy(db, adm_abspath,
                                     chosen_path, target_abspath, pool));
          {
            const svn_skel_t *work_item;

            SVN_ERR(svn_wc__wq_build_file_remove(&work_item,
                                                 db, chosen_path,
                                                 pool, pool));
            SVN_ERR(svn_wc__db_wq_add(db, adm_abspath, work_item, pool));
          }
          *merge_outcome = svn_wc_merge_merged;
          return SVN_NO_ERROR;
        }

        /* For the case of 3-way file merging, we don't
           really distinguish between these return values;
           if the callback claims to have "generally
           resolved" the situation, we still interpret
           that as "OK, we'll assume the merged version is
           good to use". */
      case svn_wc_conflict_choose_merged:
        {
          SVN_ERR(svn_wc__loggy_copy(db, adm_abspath,
                                     /* Look for callback's own
                                        merged-file first: */
                                     result->merged_file
                                       ? result->merged_file
                                       : result_target,
                                     target_abspath,
                                     pool));
          *merge_outcome = svn_wc_merge_merged;
          return SVN_NO_ERROR;
        }
      case svn_wc_conflict_choose_postpone:
      default:
        {
          /* Issue #3354: We need to install the copyfrom_text,
           * which now carries conflicts, into ACTUAL, by copying
           * it to the merge target. */
          if (copyfrom_text)
            {
              SVN_ERR(svn_wc__loggy_copy(db, adm_abspath,
                                         copyfrom_text, target_abspath,
                                         pool));
            }

#if 0
          /* ### what should this value be? no caller appears to initialize
             ### it, so we really SHOULD be setting a value here.  */
          *merge_outcome = svn_wc_merge_merged;
#endif

          /* Assume conflict remains. */
          return SVN_NO_ERROR;
        }
    }
}

/* Preserve the three pre-merge files, and modify the
   entry (mark as conflicted, track the preserved files). */
static svn_error_t*
preserve_pre_merge_files(svn_wc__db_t *db,
                         const char *left_abspath,
                         const char *right_abspath,
                         const char *target_abspath,
                         const char *left_label,
                         const char *right_label,
                         const char *target_label,
                         apr_pool_t *pool)
{
  const char *left_copy, *right_copy, *target_copy;
  const char *tmp_left, *tmp_right, *detranslated_target_copy;
  const char *dir_abspath, *target_name;
  const char *temp_dir;
  svn_wc_entry_t tmp_entry;
  svn_stringbuf_t *log_accum = NULL;

  svn_dirent_split(target_abspath, &dir_abspath, &target_name, pool);
  SVN_ERR(svn_wc__db_temp_wcroot_tempdir(&temp_dir, db, target_abspath,<commit_msg>Convert some uses of loggy_copy over to OP_FILE_INSTALL.<commit_after>/*
 * merge.c:  merging changes into a working file
 *
 * ====================================================================
 *    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 "svn_wc.h"
#include "svn_diff.h"
#include "svn_dirent_uri.h"
#include "svn_path.h"
#include "svn_pools.h"

#include "wc.h"
#include "entries.h"
#include "adm_files.h"
#include "translate.h"
#include "log.h"
#include "lock.h"
#include "workqueue.h"

#include "svn_private_config.h"



/* Return a pointer to the svn_prop_t structure from PROP_DIFF
   belonging to PROP_NAME, if any.  NULL otherwise.*/
static const svn_prop_t *
get_prop(const apr_array_header_t *prop_diff,
         const char *prop_name)
{
  if (prop_diff)
    {
      int i;
      for (i = 0; i < prop_diff->nelts; i++)
        {
          const svn_prop_t *elt = &APR_ARRAY_IDX(prop_diff, i, svn_prop_t);
          if (strcmp(elt->name,prop_name) == 0)
            return elt;
        }
    }

  return NULL;
}


/* Detranslate a working copy file MERGE_TARGET to achieve the effect of:

   1. Detranslate
   2. Install new props
   3. Retranslate
   4. Detranslate

   in 1 pass to get a file which can be compared with the left and right
   files which were created with the 'new props' above.

   Property changes make this a little complex though. Changes in

   - svn:mime-type
   - svn:eol-style
   - svn:keywords
   - svn:special

   may change the way a file is translated.

   Effect for svn:mime-type:

     The value for svn:mime-type affects the translation wrt keywords
     and eol-style settings.

   I) both old and new mime-types are texty
      -> just do the translation dance (as lined out below)

   II) the old one is texty, the new one is binary
      -> detranslate with the old eol-style and keywords
         (the new re+detranslation is a no-op)

   III) the old one is binary, the new one texty
      -> detranslate with the new eol-style
         (the old detranslation is a no-op)

   IV) the old and new ones are binary
      -> don't detranslate, just make a straight copy


   Effect for svn:eol-style

   I) On add or change use the new value

   II) otherwise: use the old value (absent means 'no translation')


   Effect for svn:keywords

     Always use old settings (re+detranslation are no-op)


   Effect for svn:special

     Always use the old settings (same reasons as for svn:keywords)

*/
static svn_error_t *
detranslate_wc_file(const char **detranslated_abspath,
                    svn_wc__db_t *db,
                    const char *target_abspath,
                    svn_boolean_t force_copy,
                    const apr_array_header_t *prop_diff,
                    const char *source_abspath,
                    apr_pool_t *result_pool,
                    apr_pool_t *scratch_pool)
{
  svn_boolean_t is_binary;
  const svn_prop_t *prop;
  svn_subst_eol_style_t style;
  const char *eol;
  apr_hash_t *keywords;
  svn_boolean_t special;

  /* Decide if the merge target currently is a text or binary file. */
  SVN_ERR(svn_wc__marked_as_binary(&is_binary, target_abspath, db,
                                   scratch_pool));

  /* See if we need to do a straight copy:
     - old and new mime-types are binary, or
     - old mime-type is binary and no new mime-type specified */
  if (is_binary
      && (((prop = get_prop(prop_diff, SVN_PROP_MIME_TYPE))
           && prop->value && svn_mime_type_is_binary(prop->value->data))
          || prop == NULL))
    {
      /* this is case IV above */
      keywords = NULL;
      special = FALSE;
      eol = NULL;
      style = svn_subst_eol_style_none;
    }
  else if ((!is_binary)
           && (prop = get_prop(prop_diff, SVN_PROP_MIME_TYPE))
           && prop->value && svn_mime_type_is_binary(prop->value->data))
    {
      /* Old props indicate texty, new props indicate binary:
         detranslate keywords and old eol-style */
      SVN_ERR(svn_wc__get_keywords(&keywords, db, target_abspath, NULL,
                                   scratch_pool, scratch_pool));
      SVN_ERR(svn_wc__get_special(&special, db, target_abspath, scratch_pool));
    }
  else
    {
      /* New props indicate texty, regardless of old props */

      /* In case the file used to be special, detranslate specially */
      SVN_ERR(svn_wc__get_special(&special, db, target_abspath, scratch_pool));

      if (special)
        {
          keywords = NULL;
          eol = NULL;
          style = svn_subst_eol_style_none;
        }
      else
        {
          /* In case a new eol style was set, use that for detranslation */
          if ((prop = get_prop(prop_diff, SVN_PROP_EOL_STYLE)) && prop->value)
            {
              /* Value added or changed */
              svn_subst_eol_style_from_value(&style, &eol, prop->value->data);
            }
          else if (!is_binary)
            SVN_ERR(svn_wc__get_eol_style(&style, &eol, db, target_abspath,
                                          scratch_pool, scratch_pool));
          else
            {
              eol = NULL;
              style = svn_subst_eol_style_none;
            }

          /* In case there were keywords, detranslate with keywords
             (iff we were texty) */
          if (!is_binary)
            SVN_ERR(svn_wc__get_keywords(&keywords, db, target_abspath, NULL,
                                         scratch_pool, scratch_pool));
          else
            keywords = NULL;
        }
    }

  /* Now, detranslate with the settings we created above */

  if (force_copy || keywords || eol || special)
    {
      const char *detranslated;

      /* Force a copy into the temporary wc area to avoid having
         temporary files created below to appear in the actual wc. */

      /* ### svn_subst_copy_and_translate3() also creates a tempfile
         ### internally.  Anyway to piggyback on that? */
      SVN_ERR(svn_io_open_unique_file3(NULL, &detranslated, NULL,
                                      svn_io_file_del_on_pool_cleanup,
                                      result_pool, scratch_pool));

      /* Always 'repair' EOLs here, so that we can apply a diff that
         changes from inconsistent newlines and no 'svn:eol-style' to
         consistent newlines and 'svn:eol-style' set.  */

      if (style == svn_subst_eol_style_native)
        eol = SVN_SUBST_NATIVE_EOL_STR;
      else if (style != svn_subst_eol_style_fixed
               && style != svn_subst_eol_style_none)
        return svn_error_create(SVN_ERR_IO_UNKNOWN_EOL, NULL, NULL);

      SVN_ERR(svn_subst_copy_and_translate3(source_abspath,
                                            detranslated,
                                            eol,
                                            TRUE /* repair */,
                                            keywords,
                                            FALSE /* contract keywords */,
                                            special,
                                            scratch_pool));

      SVN_ERR(svn_dirent_get_absolute(detranslated_abspath, detranslated,
                                      result_pool));
    }
  else
    *detranslated_abspath = apr_pstrdup(result_pool, source_abspath);

  return SVN_NO_ERROR;
}

/* Updates (by copying and translating) the eol style in
   OLD_TARGET returning the filename containing the
   correct eol style in NEW_TARGET, if an eol style
   change is contained in PROP_DIFF */
static svn_error_t *
maybe_update_target_eols(const char **new_target_abspath,
                         svn_wc__db_t *db,
                         const char *old_target_abspath,
                         const apr_array_header_t *prop_diff,
                         apr_pool_t *result_pool,
                         apr_pool_t *scratch_pool)
{
  const svn_prop_t *prop = get_prop(prop_diff, SVN_PROP_EOL_STYLE);

  if (prop && prop->value)
    {
      const char *eol;
      const char *tmp_new;

      svn_subst_eol_style_from_value(NULL, &eol, prop->value->data);
      SVN_ERR(svn_io_open_unique_file3(NULL, &tmp_new, NULL,
                                       svn_io_file_del_on_pool_cleanup,
                                       result_pool, scratch_pool));

      /* Always 'repair' EOLs here, so that we can apply a diff that
         changes from inconsistent newlines and no 'svn:eol-style' to
         consistent newlines and 'svn:eol-style' set.  */
      SVN_ERR(svn_subst_copy_and_translate3(old_target_abspath,
                                            tmp_new,
                                            eol, TRUE /* repair EOLs */,
                                            NULL, FALSE,
                                            FALSE, scratch_pool));
      *new_target_abspath = apr_pstrdup(result_pool, tmp_new);
    }
  else
    *new_target_abspath = apr_pstrdup(result_pool, old_target_abspath);

  return SVN_NO_ERROR;
}


/* Helper for do_text_merge_internal() below. */
static void
init_conflict_markers(const char **target_marker,
                      const char **left_marker,
                      const char **right_marker,
                      const char *target_label,
                      const char *left_label,
                      const char *right_label,
                      apr_pool_t *pool)
{
  /* Labels fall back to sensible defaults if not specified. */
  if (target_label)
    *target_marker = apr_psprintf(pool, "<<<<<<< %s", target_label);
  else
    *target_marker = "<<<<<<< .working";

  if (left_label)
    *left_marker = apr_psprintf(pool, "||||||| %s", left_label);
  else
    *left_marker = "||||||| .old";

  if (right_label)
    *right_marker = apr_psprintf(pool, ">>>>>>> %s", right_label);
  else
    *right_marker = ">>>>>>> .new";
}

/* Do a 3-way merge of the files at paths LEFT, DETRANSLATED_TARGET,
 * and RIGHT, using diff options provided in OPTIONS.  Store the merge
 * result in the file RESULT_F.
 * If there are conflicts, set *CONTAINS_CONFLICTS to true, and use
 * TARGET_LABEL, LEFT_LABEL, and RIGHT_LABEL as labels for conflict
 * markers.  Else, set *CONTAINS_CONFLICTS to false.
 * Do all allocations in POOL. */
static svn_error_t*
do_text_merge(svn_boolean_t *contains_conflicts,
              apr_file_t *result_f,
              const char *detranslated_target,
              const char *left,
              const char *right,
              const char *target_label,
              const char *left_label,
              const char *right_label,
              svn_diff_file_options_t *options,
              apr_pool_t *pool)
{
  svn_diff_t *diff;
  svn_stream_t *ostream;
  const char *target_marker;
  const char *left_marker;
  const char *right_marker;

  init_conflict_markers(&target_marker, &left_marker, &right_marker,
                        target_label, left_label, right_label, pool);

  SVN_ERR(svn_diff_file_diff3_2(&diff, left, detranslated_target, right,
                                options, pool));

  ostream = svn_stream_from_aprfile2(result_f, TRUE, pool);

  SVN_ERR(svn_diff_file_output_merge2(ostream, diff,
                                      left, detranslated_target, right,
                                      left_marker,
                                      target_marker,
                                      right_marker,
                                      "=======", /* separator */
                                      svn_diff_conflict_display_modified_latest,
                                      pool));
  SVN_ERR(svn_stream_close(ostream));

  *contains_conflicts = svn_diff_contains_conflicts(diff);

  return SVN_NO_ERROR;
}

/* Same as do_text_merge() above, but use the external diff3
 * command DIFF3_CMD to perform the merge.  Pass MERGE_OPTIONS
 * to the diff3 command.  Do all allocations in POOL. */
static svn_error_t*
do_text_merge_external(svn_boolean_t *contains_conflicts,
                            apr_file_t *result_f,
                            const char *detranslated_target,
                            const char *left_abspath,
                            const char *right_abspath,
                            const char *target_label,
                            const char *left_label,
                            const char *right_label,
                            const char *diff3_cmd,
                            const apr_array_header_t *merge_options,
                            apr_pool_t *pool)
{
  int exit_code;

  SVN_ERR(svn_io_run_diff3_2(&exit_code, ".",
                             detranslated_target, left_abspath, right_abspath,
                             target_label, left_label, right_label,
                             result_f, diff3_cmd,
                             merge_options, pool));

  *contains_conflicts = exit_code == 1;

  return SVN_NO_ERROR;
}

/* Loggy-copy the merge result obtained during interactive conflict
 * resolution to the file RESULT_TARGET. The merge result is expected
 * in the same directory as TARGET_ABSPATH the same basename as
 * TARGET_ABSPATH, but followed by ".edited".
 * Use LOG_ACCUM as log accumulator.  DB contains an access baton with
 * a write lock for the directory containing RESULT_TARGET.
 * Do all allocations in POOL.
 */
static svn_error_t*
save_merge_result(svn_wc__db_t *db,
                  const char *target_abspath,
                  const char *result_target,
                  apr_pool_t *pool)
{
  const char *edited_copy;
  const char *merge_dirpath;
  const char *merge_filename;

  svn_dirent_split(target_abspath, &merge_dirpath, &merge_filename, pool);

  /* ### Should use preserved-conflict-file-exts. */
  SVN_ERR(svn_io_open_uniquely_named(NULL,
                                     &edited_copy,
                                     merge_dirpath,
                                     merge_filename,
                                     ".edited",
                                     svn_io_file_del_none,
                                     pool, pool));
  SVN_ERR(svn_wc__loggy_copy(db, merge_dirpath,
                             result_target, edited_copy, pool));

  return SVN_NO_ERROR;
}

/* Deal with the RESULT of the conflict resolution callback.
 * LEFT, RIGHT, and MERGE_TARGET are the files involved in
 * the 3-way merge.  Store the result of the 3-way merge in
 * MERGE_OUTCOME.  If the callback did not provide the name to
 * a merged file, use RESULT_TARGET is a fallback.
 * DETRANSLATED_TARGET is the detranslated version of MERGE_TARGET
 * (see detranslate_wc_file() above).  OPTIONS are passed to the
 * diff3 implementation in case a 3-way  merge has to be carried out.
 * Do all allocations in POOL. */
static svn_error_t*
eval_conflict_func_result(enum svn_wc_merge_outcome_t *merge_outcome,
                          svn_wc_conflict_result_t *result,
                          svn_wc__db_t *db,
                          const char *left_abspath,
                          const char *right_abspath,
                          const char *target_abspath,
                          const char *copyfrom_text,
                          const char *adm_abspath,
                          const char *result_target,
                          const char *detranslated_target,
                          svn_diff_file_options_t *options,
                          apr_pool_t *pool)
{
  const char *install_from = NULL;
  svn_boolean_t remove_source = FALSE;

  switch (result->choice)
    {
      /* If the callback wants to use one of the fulltexts
         to resolve the conflict, so be it.*/
      case svn_wc_conflict_choose_base:
        {
          install_from = left_abspath;
          *merge_outcome = svn_wc_merge_merged;
          break;
        }
      case svn_wc_conflict_choose_theirs_full:
        {
          install_from = right_abspath;
          *merge_outcome = svn_wc_merge_merged;
          break;
        }
      case svn_wc_conflict_choose_mine_full:
        {
          /* Do nothing to merge_target, let it live untouched! */
          *merge_outcome = svn_wc_merge_merged;
          return SVN_NO_ERROR;
        }
      case svn_wc_conflict_choose_theirs_conflict:
      case svn_wc_conflict_choose_mine_conflict:
        {
          const char *chosen_path;
          const char *temp_dir;
          svn_stream_t *chosen_stream;
          svn_diff_t *diff;
          svn_diff_conflict_display_style_t style;

          style = result->choice == svn_wc_conflict_choose_theirs_conflict
                    ? svn_diff_conflict_display_latest
                    : svn_diff_conflict_display_modified;

          SVN_ERR(svn_wc__db_temp_wcroot_tempdir(&temp_dir, db, adm_abspath,
                                                 pool, pool));
          SVN_ERR(svn_stream_open_unique(&chosen_stream, &chosen_path,
                                         temp_dir,
                                         svn_io_file_del_none, pool, pool));

          SVN_ERR(svn_diff_file_diff3_2(&diff,
                                        left_abspath,
                                        detranslated_target, right_abspath,
                                        options, pool));
          SVN_ERR(svn_diff_file_output_merge2(chosen_stream, diff,
                                              left_abspath,
                                              detranslated_target,
                                              right_abspath,
                                              /* markers ignored */
                                              NULL, NULL,
                                              NULL, NULL,
                                              style,
                                              pool));
          SVN_ERR(svn_stream_close(chosen_stream));

          install_from = chosen_path;
          remove_source = TRUE;
          *merge_outcome = svn_wc_merge_merged;
          break;
        }

        /* For the case of 3-way file merging, we don't
           really distinguish between these return values;
           if the callback claims to have "generally
           resolved" the situation, we still interpret
           that as "OK, we'll assume the merged version is
           good to use". */
      case svn_wc_conflict_choose_merged:
        {
          /* Look for callback's own merged-file first:  */
          install_from = (result->merged_file
                            ? result->merged_file
                            : result_target);
          *merge_outcome = svn_wc_merge_merged;
          break;
        }
      case svn_wc_conflict_choose_postpone:
      default:
        {
#if 0
          /* ### what should this value be? no caller appears to initialize
             ### it, so we really SHOULD be setting a value here.  */
          *merge_outcome = svn_wc_merge_merged;
#endif

          /* Issue #3354: We need to install the copyfrom_text,
           * which now carries conflicts, into ACTUAL, by copying
           * it to the merge target. */
          if (copyfrom_text)
            {
              install_from = copyfrom_text;
              break;
            }

          /* Assume conflict remains. */
          return SVN_NO_ERROR;
        }
    }

  SVN_ERR_ASSERT(install_from != NULL);

  {
    const svn_skel_t *work_item;

    SVN_ERR(svn_wc__wq_build_file_install(&work_item,
                                          db, target_abspath,
                                          install_from,
                                          FALSE /* use_commit_times */,
                                          FALSE /* record_fileinfo */,
                                          pool, pool));
    SVN_ERR(svn_wc__db_wq_add(db, adm_abspath, work_item, pool));

    if (remove_source)
      {
        SVN_ERR(svn_wc__wq_build_file_remove(&work_item,
                                             db, install_from,
                                             pool, pool));
        SVN_ERR(svn_wc__db_wq_add(db, adm_abspath, work_item, pool));
      }
  }

  return SVN_NO_ERROR;
}

/* Preserve the three pre-merge files, and modify the
   entry (mark as conflicted, track the preserved files). */
static svn_error_t*
preserve_pre_merge_files(svn_wc__db_t *db,
                         const char *left_abspath,
                         const char *right_abspath,
                         const char *target_abspath,
                         const char *left_label,
                         const char *right_label,
                         const char *target_label,
                         apr_pool_t *pool)
{
  const char *left_copy, *right_copy, *target_copy;
  const char *tmp_left, *tmp_right, *detranslated_target_copy;
  const char *dir_abspath, *target_name;
  const char *temp_dir;
  svn_wc_entry_t tmp_entry;
  svn_stringbuf_t *log_accum = NULL;

  svn_dirent_split(target_abspath, &dir_abspath, &target_name, pool);
  SVN_ERR(svn_wc__db_temp_wcroot_tempdir(&temp_dir, db, target_abspath,
{'fraction_non_alphanumeric': 0.07583814501220987, 'fraction_numerical': 0.0019170641531825548, 'mean_word_length': 1.7234756666045123, 'pattern_counts': {'":': 0, '<': 33, '<?xml version=': 0, '>': 75, 'https://': 0, 'lorem ipsum': 0, 'www.': 2, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 179, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '6860514', 'n_tokens_mistral': 12902, 'n_tokens_neox': 12417, 'n_words': 3311}