<commit_before>/*
 * log.c:  handle the adm area's log file.
 *
 * ====================================================================
 * Copyright (c) 2000-2004 CollabNet.  All rights reserved.
 *
 * This software is licensed as described in the file COPYING, which
 * you should have received as part of this distribution.  The terms
 * are also available at http://subversion.tigris.org/license-1.html.
 * If newer versions of this license are posted there, you may use a
 * newer version instead, at your option.
 *
 * This software consists of voluntary contributions made by many
 * individuals.  For exact contribution history, see the revision
 * history and logs, available at http://subversion.tigris.org/.
 * ====================================================================
 */




#include <string.h>

#include <apr_pools.h>
#include <apr_strings.h>
#include <apr_thread_proc.h>

#include "svn_wc.h"
#include "svn_error.h"
#include "svn_string.h"
#include "svn_xml.h"
#include "svn_pools.h"
#include "svn_io.h"
#include "svn_path.h"
#include "svn_time.h"

#include "wc.h"
#include "log.h"
#include "props.h"
#include "adm_files.h"
#include "entries.h"
#include "translate.h"
#include "questions.h"

#include "svn_private_config.h"



/*** Userdata for the callbacks. ***/
struct log_runner
{
  apr_pool_t *pool;
  svn_xml_parser_t *parser;
  svn_boolean_t entries_modified;
  svn_wc_adm_access_t *adm_access;  /* the dir in which all this happens */
  const char *diff3_cmd;            /* external diff3 cmd, or null if none */

  /* Which top-level log element we're on for this logfile.  Some
     callers care whether a failure happened on the first element or
     on some later element (e.g., 'svn cleanup').

     This is initialized to 0 when the log_runner is created, and
     incremented every time start_handler() is called. */
  int count;
};




/*** The XML handlers. ***/

/* Used by file_xfer_under_path(). */
enum svn_wc__xfer_action {
  svn_wc__xfer_cp,
  svn_wc__xfer_mv,
  svn_wc__xfer_append,
  svn_wc__xfer_cp_and_translate,
  svn_wc__xfer_cp_and_detranslate
};


/* Perform some sort of copy-related ACTION on NAME and DEST:

      svn_wc__xfer_cp:                 just do a copy of NAME to DEST.
      svn_wc__xfer_mv:                 do a copy, then remove NAME.
      svn_wc__xfer_append:             append contents of NAME to DEST
      svn_wc__xfer_cp_and_translate:   copy NAME to DEST, doing any eol
                                       and keyword expansion according to
                                       the current property vals of DEST.
      svn_wc__xfer_cp_and_detranslate: copy NAME to DEST, converting to LF
                                       and contracting keywords according to
                                       the current property vals of NAME.
*/
static svn_error_t *
file_xfer_under_path (svn_wc_adm_access_t *adm_access,
                      const char *name,
                      const char *dest,
                      enum svn_wc__xfer_action action,
                      apr_pool_t *pool)
{
  svn_error_t *err;
  const char *full_from_path, *full_dest_path;

  full_from_path = svn_path_join (svn_wc_adm_access_path (adm_access), name,
                                  pool);
  full_dest_path = svn_path_join (svn_wc_adm_access_path (adm_access), dest,
                                  pool);

  switch (action)
    {
    case svn_wc__xfer_append:
      return svn_io_append_file (full_from_path, full_dest_path, pool);
      
    case svn_wc__xfer_cp:
      return svn_io_copy_file (full_from_path, full_dest_path, FALSE, pool);

    case svn_wc__xfer_cp_and_translate:
      {
        svn_subst_keywords_t *keywords;
        const char *eol_str;
        svn_boolean_t special;

        /* Note that this action takes properties from dest, not source. */
        SVN_ERR (svn_wc__get_keywords (&keywords, full_dest_path, adm_access,
                                       NULL, pool));
        SVN_ERR (svn_wc__get_eol_style (NULL, &eol_str, full_dest_path,
                                        adm_access, pool));
        SVN_ERR (svn_wc__get_special (&special, full_dest_path, adm_access,
                                      pool));

        SVN_ERR (svn_subst_copy_and_translate2 (full_from_path,
                                                full_dest_path,
                                                eol_str,
                                                TRUE,
                                                keywords,
                                                TRUE,
                                                special,
                                                pool));

        /* After copying, set the file executable if props dictate. */
        return svn_wc__maybe_set_executable (NULL, full_dest_path, adm_access,
                                             pool);
      }

    case svn_wc__xfer_cp_and_detranslate:
      {
        svn_subst_keywords_t *keywords;
        const char *eol_str;
        svn_boolean_t special;

        /* Note that this action takes properties from source, not dest. */
        SVN_ERR (svn_wc__get_keywords (&keywords, full_from_path, adm_access,
                                       NULL, pool));
        SVN_ERR (svn_wc__get_eol_style (NULL, &eol_str, full_from_path,
                                        adm_access, pool));
        SVN_ERR (svn_wc__get_special (&special, full_from_path, adm_access,
                                      pool));

        /* If any specific eol style was indicated, then detranslate
           back to repository normal form ("\n"), repairingly.  But if
           no style indicated, don't touch line endings at all. */
        return svn_subst_copy_and_translate2 (full_from_path,
                                              full_dest_path,
                                              (eol_str ? "\n" : NULL),
                                              (eol_str ? TRUE : FALSE),  
                                              keywords,
                                              FALSE, /* contract keywords */
                                              special,
                                              pool);
      }

    case svn_wc__xfer_mv:
      SVN_ERR (svn_wc__prep_file_for_replacement (full_dest_path, TRUE, pool));

      err = svn_io_file_rename (full_from_path,
                                full_dest_path, pool);

      /* If we got an ENOENT, that's ok;  the move has probably
         already completed in an earlier run of this log.  */
      if (err)
        {
          if (! APR_STATUS_IS_ENOENT(err->apr_err))
            return svn_error_quick_wrap (err, _("Can't move source to dest"));
          svn_error_clear (err);
        }
    }

  return SVN_NO_ERROR;
}


/* If new text was committed, then replace the text base for
 * newly-committed file NAME in directory PATH with the new
 * post-commit text base, which is waiting in the adm tmp area in
 * detranslated form.
 *
 * If eol and/or keyword translation would cause the working file to
 * change, then overwrite the working file with a translated copy of
 * the new text base (but only if the translated copy differs from the
 * current working file -- if they are the same, do nothing, to avoid
 * clobbering timestamps unnecessarily).
 *
 * If the executable property is set, the set working file's
 * executable.
 *
 * If the working file was re-translated or had executability set,
 * then set OVERWROTE_WORKING to TRUE.  If the working file isn't
 * touched at all, then set to FALSE.
 * 
 * Use POOL for any temporary allocation.
 */
static svn_error_t *
install_committed_file (svn_boolean_t *overwrote_working,
                        svn_wc_adm_access_t *adm_access,
                        const char *name,
                        apr_pool_t *pool)
{
  const char *filepath;
  const char *tmp_text_base;
  svn_node_kind_t kind;
  svn_subst_keywords_t *keywords;
  apr_file_t *ignored;
  svn_boolean_t same, did_set;
  const char *tmp_wfile, *pdir, *bname;
  const char *eol_str;
  svn_boolean_t special;

  /* start off assuming that the working file isn't touched. */
  *overwrote_working = FALSE;

  filepath = svn_path_join (svn_wc_adm_access_path (adm_access), name, pool);

  /* In the commit, newlines and keywords may have been
   * canonicalized and/or contracted... Or they may not have
   * been.  It's kind of hard to know.  Here's how we find out:
   *
   *    1. Make a translated tmp copy of the committed text base.
   *       Or, if no committed text base exists (the commit must have
   *       been a propchange only), make a translated tmp copy of the
   *       working file.
   *    2. Compare the translated tmpfile to the working file.
   *    3. If different, copy the tmpfile over working file.
   *
   * This means we only rewrite the working file if we absolutely
   * have to, which is good because it avoids changing the file's
   * timestamp unless necessary, so editors aren't tempted to
   * reread the file if they don't really need to.
   */

  /* start off getting the latest translation prop values. */
  SVN_ERR (svn_wc__get_eol_style (NULL, &eol_str, filepath, adm_access, pool));
  SVN_ERR (svn_wc__get_keywords (&keywords, filepath, adm_access, NULL, pool));
  SVN_ERR (svn_wc__get_special (&special, filepath, adm_access, pool));

  svn_path_split (filepath, &pdir, &bname, pool);
  tmp_wfile = svn_wc__adm_path (pdir, TRUE, pool, bname, NULL);
  
  SVN_ERR (svn_io_open_unique_file (&ignored, &tmp_wfile,
                                    tmp_wfile, SVN_WC__TMP_EXT,
                                    FALSE, pool));
  SVN_ERR (svn_io_file_close (ignored, pool));

  /* Is there a tmp_text_base that needs to be installed?  */
  tmp_text_base = svn_wc__text_base_path (filepath, 1, pool);
  SVN_ERR (svn_io_check_path (tmp_text_base, &kind, pool));

  if (kind == svn_node_file)
    SVN_ERR (svn_subst_copy_and_translate2 (tmp_text_base,
                                            tmp_wfile,
                                            eol_str,
                                            FALSE, /* don't repair eol */
                                            keywords,
                                            TRUE, /* expand keywords */
                                            special,
                                            pool));
  else
    SVN_ERR (svn_subst_copy_and_translate2 (filepath,
                                            tmp_wfile,
                                            eol_str,
                                            FALSE, /* don't repair eol */
                                            keywords,
                                            TRUE, /* expand keywords */
                                            special,
                                            pool));

  if (! special)
    {
      SVN_ERR (svn_io_files_contents_same_p (&same, tmp_wfile, filepath, pool));
    }
  else
    {
      same = TRUE;
    }
  
  if (! same)
    {
      SVN_ERR (svn_io_copy_file (tmp_wfile, filepath, FALSE, pool));
      *overwrote_working = TRUE;
    }

  SVN_ERR (svn_io_remove_file (tmp_wfile, pool));

  /* Set the working file's execute bit if props dictate. */
  SVN_ERR (svn_wc__maybe_set_executable (&did_set, filepath, adm_access, pool));
  if (did_set)
    /* okay, so we didn't -overwrite- the working file, but we changed
       its timestamp, which is the point of returning this flag. :-) */
    *overwrote_working = TRUE;

  /* Install the new text base if one is waiting. */
  if (kind == svn_node_file)  /* tmp_text_base exists */
    SVN_ERR (svn_wc__sync_text_base (filepath, pool));

  return SVN_NO_ERROR;
}


/* Sometimes, documentation would only confuse matters. */
static apr_status_t
pick_error_code (struct log_runner *loggy)
{
  if (loggy->count <= 1)
    return SVN_ERR_WC_BAD_ADM_LOG_START;
  else
    return SVN_ERR_WC_BAD_ADM_LOG;
}

static void
signal_error (struct log_runner *loggy, svn_error_t *err)
{
  svn_xml_signal_bailout
    (svn_error_createf (pick_error_code (loggy), err,
                        _("In directory '%s'"),
                        svn_wc_adm_access_path (loggy->adm_access)),
     loggy->parser);
}






/*** Dispatch on the xml opening tag. ***/

static svn_error_t *
log_do_merge (struct log_runner *loggy,
              const char *name,
              const char **atts)
{
  const char *left, *right;
  const char *left_label, *right_label, *target_label;
  enum svn_wc_merge_outcome_t merge_outcome;

  /* NAME is the basename of our merge_target.  Pull out LEFT and RIGHT. */
  left = svn_xml_get_attr_value (SVN_WC__LOG_ATTR_ARG_1, atts);
  if (! left)
    return svn_error_createf (pick_error_code (loggy), NULL,
                              _("Missing 'left' attribute in '%s'"),
                              svn_wc_adm_access_path (loggy->adm_access));
  right = svn_xml_get_attr_value (SVN_WC__LOG_ATTR_ARG_2, atts);
  if (! right)
    return svn_error_createf (pick_error_code (loggy), NULL,
                              _("Missing 'right' attribute in '%s'"),
                              svn_wc_adm_access_path (loggy->adm_access));

  /* Grab all three labels too.  If non-existent, we'll end up passing
     NULLs to svn_wc_merge, which is fine -- it will use default
     labels. */
  left_label = svn_xml_get_attr_value (SVN_WC__LOG_ATTR_ARG_3, atts);
  right_label = svn_xml_get_attr_value (SVN_WC__LOG_ATTR_ARG_4, atts);
  target_label = svn_xml_get_attr_value (SVN_WC__LOG_ATTR_ARG_5, atts);

  /* Convert the 3 basenames into full paths. */
  left = svn_path_join (svn_wc_adm_access_path (loggy->adm_access), left,
                        loggy->pool);
  right = svn_path_join (svn_wc_adm_access_path (loggy->adm_access), right,
                         loggy->pool);
  name = svn_path_join (svn_wc_adm_access_path (loggy->adm_access), name,
                        loggy->pool);

  /* Now do the merge with our full paths. */
  SVN_ERR (svn_wc_merge (left, right, name, loggy->adm_access,
                         left_label, right_label, target_label,
                         FALSE, &merge_outcome, loggy->diff3_cmd, 
                         loggy->pool));

  return SVN_NO_ERROR;
}


static svn_error_t *
log_do_file_xfer (struct log_runner *loggy,
                  const char *name,
                  enum svn_wc__xfer_action action,
                  const char **atts)
{
  svn_error_t *err;
  const char *dest = NULL;

  /* We have the name (src), and the destination is absolutely required. */
  dest = svn_xml_get_attr_value (SVN_WC__LOG_ATTR_DEST, atts);
  if (! dest)
    return svn_error_createf (pick_error_code (loggy), NULL,
                              _("Missing 'dest' attribute in '%s'"),
                              svn_wc_adm_access_path (loggy->adm_access));

  err = file_xfer_under_path (loggy->adm_access, name, dest, action,
                              loggy->pool);
  if (err)
    signal_error (loggy, err);

  return SVN_NO_ERROR;
}

/* Make file NAME in log's CWD readonly */
static svn_error_t *
log_do_file_readonly (struct log_runner *loggy,
                      const char *name)
{
  const char *full_path
    = svn_path_join (svn_wc_adm_access_path (loggy->adm_access), name,
                     loggy->pool);

  SVN_ERR (svn_io_set_file_read_only (full_path, FALSE, loggy->pool));

  return SVN_NO_ERROR;
}


/* Set file NAME in log's CWD to timestamp value in ATTS. */
static svn_error_t *
log_do_file_timestamp (struct log_runner *loggy,
                       const char *name,                       
                       const char **atts)
{
  apr_time_t timestamp;
  svn_boolean_t special;
  const char *full_path
    = svn_path_join (svn_wc_adm_access_path (loggy->adm_access), name,
                     loggy->pool);

  const char *timestamp_string
    = svn_xml_get_attr_value (SVN_WC__LOG_ATTR_TIMESTAMP, atts);
  if (! timestamp_string)
    return svn_error_createf (pick_error_code (loggy), NULL,
                              _("Missing 'timestamp' attribute in '%s'"),
                              svn_wc_adm_access_path (loggy->adm_access));

  /* Do not set the timestamp on special files. */
  SVN_ERR (svn_wc__get_special (&special, name, loggy->adm_access,
                                loggy->pool));
  
  if (! special)
    {
      SVN_ERR (svn_time_from_cstring (&timestamp, timestamp_string,
                                      loggy->pool));
      
      SVN_ERR (svn_io_set_file_affected_time (timestamp, full_path,
                                              loggy->pool));
    }

  return SVN_NO_ERROR;
}


/* Remove file NAME in log's CWD. */
static svn_error_t *
log_do_rm (struct log_runner *loggy, const char *name)
{
  const char *full_path<commit_msg>Apparently, when installing new files during a checkout, the timestamp is set on a file before the properties are completely installed. Therefore, using svn_wc__get_special won't work to determine special status.  This patch fixes that case by doing an actual stat to determine if the on disk path is a special one.  If it is, do not set the timestamp.<commit_after>/*
 * log.c:  handle the adm area's log file.
 *
 * ====================================================================
 * Copyright (c) 2000-2004 CollabNet.  All rights reserved.
 *
 * This software is licensed as described in the file COPYING, which
 * you should have received as part of this distribution.  The terms
 * are also available at http://subversion.tigris.org/license-1.html.
 * If newer versions of this license are posted there, you may use a
 * newer version instead, at your option.
 *
 * This software consists of voluntary contributions made by many
 * individuals.  For exact contribution history, see the revision
 * history and logs, available at http://subversion.tigris.org/.
 * ====================================================================
 */




#include <string.h>

#include <apr_pools.h>
#include <apr_strings.h>
#include <apr_thread_proc.h>

#include "svn_wc.h"
#include "svn_error.h"
#include "svn_string.h"
#include "svn_xml.h"
#include "svn_pools.h"
#include "svn_io.h"
#include "svn_path.h"
#include "svn_time.h"

#include "wc.h"
#include "log.h"
#include "props.h"
#include "adm_files.h"
#include "entries.h"
#include "translate.h"
#include "questions.h"

#include "svn_private_config.h"



/*** Userdata for the callbacks. ***/
struct log_runner
{
  apr_pool_t *pool;
  svn_xml_parser_t *parser;
  svn_boolean_t entries_modified;
  svn_wc_adm_access_t *adm_access;  /* the dir in which all this happens */
  const char *diff3_cmd;            /* external diff3 cmd, or null if none */

  /* Which top-level log element we're on for this logfile.  Some
     callers care whether a failure happened on the first element or
     on some later element (e.g., 'svn cleanup').

     This is initialized to 0 when the log_runner is created, and
     incremented every time start_handler() is called. */
  int count;
};




/*** The XML handlers. ***/

/* Used by file_xfer_under_path(). */
enum svn_wc__xfer_action {
  svn_wc__xfer_cp,
  svn_wc__xfer_mv,
  svn_wc__xfer_append,
  svn_wc__xfer_cp_and_translate,
  svn_wc__xfer_cp_and_detranslate
};


/* Perform some sort of copy-related ACTION on NAME and DEST:

      svn_wc__xfer_cp:                 just do a copy of NAME to DEST.
      svn_wc__xfer_mv:                 do a copy, then remove NAME.
      svn_wc__xfer_append:             append contents of NAME to DEST
      svn_wc__xfer_cp_and_translate:   copy NAME to DEST, doing any eol
                                       and keyword expansion according to
                                       the current property vals of DEST.
      svn_wc__xfer_cp_and_detranslate: copy NAME to DEST, converting to LF
                                       and contracting keywords according to
                                       the current property vals of NAME.
*/
static svn_error_t *
file_xfer_under_path (svn_wc_adm_access_t *adm_access,
                      const char *name,
                      const char *dest,
                      enum svn_wc__xfer_action action,
                      apr_pool_t *pool)
{
  svn_error_t *err;
  const char *full_from_path, *full_dest_path;

  full_from_path = svn_path_join (svn_wc_adm_access_path (adm_access), name,
                                  pool);
  full_dest_path = svn_path_join (svn_wc_adm_access_path (adm_access), dest,
                                  pool);

  switch (action)
    {
    case svn_wc__xfer_append:
      return svn_io_append_file (full_from_path, full_dest_path, pool);
      
    case svn_wc__xfer_cp:
      return svn_io_copy_file (full_from_path, full_dest_path, FALSE, pool);

    case svn_wc__xfer_cp_and_translate:
      {
        svn_subst_keywords_t *keywords;
        const char *eol_str;
        svn_boolean_t special;

        /* Note that this action takes properties from dest, not source. */
        SVN_ERR (svn_wc__get_keywords (&keywords, full_dest_path, adm_access,
                                       NULL, pool));
        SVN_ERR (svn_wc__get_eol_style (NULL, &eol_str, full_dest_path,
                                        adm_access, pool));
        SVN_ERR (svn_wc__get_special (&special, full_dest_path, adm_access,
                                      pool));

        SVN_ERR (svn_subst_copy_and_translate2 (full_from_path,
                                                full_dest_path,
                                                eol_str,
                                                TRUE,
                                                keywords,
                                                TRUE,
                                                special,
                                                pool));

        /* After copying, set the file executable if props dictate. */
        return svn_wc__maybe_set_executable (NULL, full_dest_path, adm_access,
                                             pool);
      }

    case svn_wc__xfer_cp_and_detranslate:
      {
        svn_subst_keywords_t *keywords;
        const char *eol_str;
        svn_boolean_t special;

        /* Note that this action takes properties from source, not dest. */
        SVN_ERR (svn_wc__get_keywords (&keywords, full_from_path, adm_access,
                                       NULL, pool));
        SVN_ERR (svn_wc__get_eol_style (NULL, &eol_str, full_from_path,
                                        adm_access, pool));
        SVN_ERR (svn_wc__get_special (&special, full_from_path, adm_access,
                                      pool));

        /* If any specific eol style was indicated, then detranslate
           back to repository normal form ("\n"), repairingly.  But if
           no style indicated, don't touch line endings at all. */
        return svn_subst_copy_and_translate2 (full_from_path,
                                              full_dest_path,
                                              (eol_str ? "\n" : NULL),
                                              (eol_str ? TRUE : FALSE),  
                                              keywords,
                                              FALSE, /* contract keywords */
                                              special,
                                              pool);
      }

    case svn_wc__xfer_mv:
      SVN_ERR (svn_wc__prep_file_for_replacement (full_dest_path, TRUE, pool));

      err = svn_io_file_rename (full_from_path,
                                full_dest_path, pool);

      /* If we got an ENOENT, that's ok;  the move has probably
         already completed in an earlier run of this log.  */
      if (err)
        {
          if (! APR_STATUS_IS_ENOENT(err->apr_err))
            return svn_error_quick_wrap (err, _("Can't move source to dest"));
          svn_error_clear (err);
        }
    }

  return SVN_NO_ERROR;
}


/* If new text was committed, then replace the text base for
 * newly-committed file NAME in directory PATH with the new
 * post-commit text base, which is waiting in the adm tmp area in
 * detranslated form.
 *
 * If eol and/or keyword translation would cause the working file to
 * change, then overwrite the working file with a translated copy of
 * the new text base (but only if the translated copy differs from the
 * current working file -- if they are the same, do nothing, to avoid
 * clobbering timestamps unnecessarily).
 *
 * If the executable property is set, the set working file's
 * executable.
 *
 * If the working file was re-translated or had executability set,
 * then set OVERWROTE_WORKING to TRUE.  If the working file isn't
 * touched at all, then set to FALSE.
 * 
 * Use POOL for any temporary allocation.
 */
static svn_error_t *
install_committed_file (svn_boolean_t *overwrote_working,
                        svn_wc_adm_access_t *adm_access,
                        const char *name,
                        apr_pool_t *pool)
{
  const char *filepath;
  const char *tmp_text_base;
  svn_node_kind_t kind;
  svn_subst_keywords_t *keywords;
  apr_file_t *ignored;
  svn_boolean_t same, did_set;
  const char *tmp_wfile, *pdir, *bname;
  const char *eol_str;
  svn_boolean_t special;

  /* start off assuming that the working file isn't touched. */
  *overwrote_working = FALSE;

  filepath = svn_path_join (svn_wc_adm_access_path (adm_access), name, pool);

  /* In the commit, newlines and keywords may have been
   * canonicalized and/or contracted... Or they may not have
   * been.  It's kind of hard to know.  Here's how we find out:
   *
   *    1. Make a translated tmp copy of the committed text base.
   *       Or, if no committed text base exists (the commit must have
   *       been a propchange only), make a translated tmp copy of the
   *       working file.
   *    2. Compare the translated tmpfile to the working file.
   *    3. If different, copy the tmpfile over working file.
   *
   * This means we only rewrite the working file if we absolutely
   * have to, which is good because it avoids changing the file's
   * timestamp unless necessary, so editors aren't tempted to
   * reread the file if they don't really need to.
   */

  /* start off getting the latest translation prop values. */
  SVN_ERR (svn_wc__get_eol_style (NULL, &eol_str, filepath, adm_access, pool));
  SVN_ERR (svn_wc__get_keywords (&keywords, filepath, adm_access, NULL, pool));
  SVN_ERR (svn_wc__get_special (&special, filepath, adm_access, pool));

  svn_path_split (filepath, &pdir, &bname, pool);
  tmp_wfile = svn_wc__adm_path (pdir, TRUE, pool, bname, NULL);
  
  SVN_ERR (svn_io_open_unique_file (&ignored, &tmp_wfile,
                                    tmp_wfile, SVN_WC__TMP_EXT,
                                    FALSE, pool));
  SVN_ERR (svn_io_file_close (ignored, pool));

  /* Is there a tmp_text_base that needs to be installed?  */
  tmp_text_base = svn_wc__text_base_path (filepath, 1, pool);
  SVN_ERR (svn_io_check_path (tmp_text_base, &kind, pool));

  if (kind == svn_node_file)
    SVN_ERR (svn_subst_copy_and_translate2 (tmp_text_base,
                                            tmp_wfile,
                                            eol_str,
                                            FALSE, /* don't repair eol */
                                            keywords,
                                            TRUE, /* expand keywords */
                                            special,
                                            pool));
  else
    SVN_ERR (svn_subst_copy_and_translate2 (filepath,
                                            tmp_wfile,
                                            eol_str,
                                            FALSE, /* don't repair eol */
                                            keywords,
                                            TRUE, /* expand keywords */
                                            special,
                                            pool));

  if (! special)
    {
      SVN_ERR (svn_io_files_contents_same_p (&same, tmp_wfile, filepath, pool));
    }
  else
    {
      same = TRUE;
    }
  
  if (! same)
    {
      SVN_ERR (svn_io_copy_file (tmp_wfile, filepath, FALSE, pool));
      *overwrote_working = TRUE;
    }

  SVN_ERR (svn_io_remove_file (tmp_wfile, pool));

  /* Set the working file's execute bit if props dictate. */
  SVN_ERR (svn_wc__maybe_set_executable (&did_set, filepath, adm_access, pool));
  if (did_set)
    /* okay, so we didn't -overwrite- the working file, but we changed
       its timestamp, which is the point of returning this flag. :-) */
    *overwrote_working = TRUE;

  /* Install the new text base if one is waiting. */
  if (kind == svn_node_file)  /* tmp_text_base exists */
    SVN_ERR (svn_wc__sync_text_base (filepath, pool));

  return SVN_NO_ERROR;
}


/* Sometimes, documentation would only confuse matters. */
static apr_status_t
pick_error_code (struct log_runner *loggy)
{
  if (loggy->count <= 1)
    return SVN_ERR_WC_BAD_ADM_LOG_START;
  else
    return SVN_ERR_WC_BAD_ADM_LOG;
}

static void
signal_error (struct log_runner *loggy, svn_error_t *err)
{
  svn_xml_signal_bailout
    (svn_error_createf (pick_error_code (loggy), err,
                        _("In directory '%s'"),
                        svn_wc_adm_access_path (loggy->adm_access)),
     loggy->parser);
}






/*** Dispatch on the xml opening tag. ***/

static svn_error_t *
log_do_merge (struct log_runner *loggy,
              const char *name,
              const char **atts)
{
  const char *left, *right;
  const char *left_label, *right_label, *target_label;
  enum svn_wc_merge_outcome_t merge_outcome;

  /* NAME is the basename of our merge_target.  Pull out LEFT and RIGHT. */
  left = svn_xml_get_attr_value (SVN_WC__LOG_ATTR_ARG_1, atts);
  if (! left)
    return svn_error_createf (pick_error_code (loggy), NULL,
                              _("Missing 'left' attribute in '%s'"),
                              svn_wc_adm_access_path (loggy->adm_access));
  right = svn_xml_get_attr_value (SVN_WC__LOG_ATTR_ARG_2, atts);
  if (! right)
    return svn_error_createf (pick_error_code (loggy), NULL,
                              _("Missing 'right' attribute in '%s'"),
                              svn_wc_adm_access_path (loggy->adm_access));

  /* Grab all three labels too.  If non-existent, we'll end up passing
     NULLs to svn_wc_merge, which is fine -- it will use default
     labels. */
  left_label = svn_xml_get_attr_value (SVN_WC__LOG_ATTR_ARG_3, atts);
  right_label = svn_xml_get_attr_value (SVN_WC__LOG_ATTR_ARG_4, atts);
  target_label = svn_xml_get_attr_value (SVN_WC__LOG_ATTR_ARG_5, atts);

  /* Convert the 3 basenames into full paths. */
  left = svn_path_join (svn_wc_adm_access_path (loggy->adm_access), left,
                        loggy->pool);
  right = svn_path_join (svn_wc_adm_access_path (loggy->adm_access), right,
                         loggy->pool);
  name = svn_path_join (svn_wc_adm_access_path (loggy->adm_access), name,
                        loggy->pool);

  /* Now do the merge with our full paths. */
  SVN_ERR (svn_wc_merge (left, right, name, loggy->adm_access,
                         left_label, right_label, target_label,
                         FALSE, &merge_outcome, loggy->diff3_cmd, 
                         loggy->pool));

  return SVN_NO_ERROR;
}


static svn_error_t *
log_do_file_xfer (struct log_runner *loggy,
                  const char *name,
                  enum svn_wc__xfer_action action,
                  const char **atts)
{
  svn_error_t *err;
  const char *dest = NULL;

  /* We have the name (src), and the destination is absolutely required. */
  dest = svn_xml_get_attr_value (SVN_WC__LOG_ATTR_DEST, atts);
  if (! dest)
    return svn_error_createf (pick_error_code (loggy), NULL,
                              _("Missing 'dest' attribute in '%s'"),
                              svn_wc_adm_access_path (loggy->adm_access));

  err = file_xfer_under_path (loggy->adm_access, name, dest, action,
                              loggy->pool);
  if (err)
    signal_error (loggy, err);

  return SVN_NO_ERROR;
}

/* Make file NAME in log's CWD readonly */
static svn_error_t *
log_do_file_readonly (struct log_runner *loggy,
                      const char *name)
{
  const char *full_path
    = svn_path_join (svn_wc_adm_access_path (loggy->adm_access), name,
                     loggy->pool);

  SVN_ERR (svn_io_set_file_read_only (full_path, FALSE, loggy->pool));

  return SVN_NO_ERROR;
}


/* Set file NAME in log's CWD to timestamp value in ATTS. */
static svn_error_t *
log_do_file_timestamp (struct log_runner *loggy,
                       const char *name,                       
                       const char **atts)
{
  apr_time_t timestamp;
  svn_node_kind_t kind;
  const char *full_path
    = svn_path_join (svn_wc_adm_access_path (loggy->adm_access), name,
                     loggy->pool);

  const char *timestamp_string
    = svn_xml_get_attr_value (SVN_WC__LOG_ATTR_TIMESTAMP, atts);
  if (! timestamp_string)
    return svn_error_createf (pick_error_code (loggy), NULL,
                              _("Missing 'timestamp' attribute in '%s'"),
                              svn_wc_adm_access_path (loggy->adm_access));

  /* Do not set the timestamp on special files. */
  SVN_ERR (svn_io_check_special_path (name, &kind, loggy->pool));
  
  if (kind != svn_node_special)
    {
      SVN_ERR (svn_time_from_cstring (&timestamp, timestamp_string,
                                      loggy->pool));
      
      SVN_ERR (svn_io_set_file_affected_time (timestamp, full_path,
                                              loggy->pool));
    }

  return SVN_NO_ERROR;
}


/* Remove file NAME in log's CWD. */
static svn_error_t *
log_do_rm (struct log_runner *loggy, const char *name)
{
  const char *full_path
{'fraction_non_alphanumeric': 0.08536621251802372, 'fraction_numerical': 0.0016478827649118676, 'mean_word_length': 1.9805297316260306, 'pattern_counts': {'":': 0, '<': 13, '<?xml version=': 0, '>': 66, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 22}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 158, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '634427', 'n_tokens_mistral': 10535, 'n_tokens_neox': 10045, 'n_words': 2895}