<commit_before>/*
 * log.c:  handle the adm area's log file.
 *
 * ====================================================================
 * Copyright (c) 2000-2001 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 "wc.h"




/*** Userdata for the callbacks. ***/
struct log_runner
{
  apr_pool_t *pool;
  svn_xml_parser_t *parser;
  svn_stringbuf_t *path;  /* the dir in which this is all happening */
};




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

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



/* Copy (or rename, if RENAME is non-zero) NAME to DEST, assuming that
   PATH is the common parent of both locations. 

   If ACTION is 'cp', then do translation IFF any of EOL_STR,
   REVISION, DATE, AUTHOR, or URL are non-NULL.  When doing
   translation, setting REPAIR indicates that inconsistent line
   endings in SRC are translated to EOL_STR; else error is returned.
   (For more info, read the docstring of svn_io_copy_and_translate).
*/
static svn_error_t *
file_xfer_under_path (svn_stringbuf_t *path,
                      const char *name,
                      const char *dest,
                      enum svn_wc__xfer_action action,
                      const char *eol_str,
                      svn_boolean_t repair,
                      const char *revision,
                      const char *date,
                      const char *author,
                      const char *url,                      
                      apr_pool_t *pool)
{
  apr_status_t status;
  svn_stringbuf_t *full_from_path, *full_dest_path;

  full_from_path = svn_stringbuf_dup (path, pool);
  full_dest_path = svn_stringbuf_dup (path, pool);

  svn_path_add_component_nts (full_from_path, name, svn_path_local_style);
  svn_path_add_component_nts (full_dest_path, dest, svn_path_local_style);

  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_and_translate (full_from_path->data,
                                        full_dest_path->data,
                                        eol_str,
                                        repair,
                                        revision,
                                        date,
                                        author,
                                        url,
                                        TRUE, /* ### todo: expand? */
                                        pool);

    case svn_wc__xfer_mv:
      status = apr_file_rename (full_from_path->data,
                              full_dest_path->data, pool);
      if (status)
        return svn_error_createf (status, 0, NULL, pool,
                                  "file_xfer_under_path: "
                                  "can't move %s to %s",
                                  name, dest);
  }

  return SVN_NO_ERROR;
}



static svn_error_t *
replace_text_base (svn_stringbuf_t *path,
                   const char *name,
                   apr_pool_t *pool)
{
  svn_stringbuf_t *filepath;
  svn_stringbuf_t *tmp_text_base;
  svn_error_t *err;
  enum svn_node_kind kind;

  filepath = svn_stringbuf_dup (path, pool);
  svn_path_add_component_nts (filepath, name, svn_path_local_style);

  tmp_text_base = svn_wc__text_base_path (filepath, 1, pool);
  err = svn_io_check_path (tmp_text_base, &kind, pool);
  if (err)
    return err;

  if (kind == svn_node_none)
    return SVN_NO_ERROR;  /* tolerate mop-up calls gracefully */
  else
    {
      enum svn_wc__eol_style eol_style;
      const char *eol_str;

      SVN_ERR (svn_wc__get_eol_style (&eol_style, &eol_str,
                                      filepath->data, pool));

      /* If the eol-style is 'fixed', then the committed
         tmp/text-base/file need to be copied back out on top of the
         working file in the correct 'fixed' eol style.  We do this
         because it may have been repaired during commit, and we want
         the working file to look exactly like text-base after a
         commit. */
      if (eol_style == svn_wc__eol_style_fixed)
        SVN_ERR (svn_io_copy_and_translate (tmp_text_base->data,
                                            filepath->data,
                                            eol_str, TRUE /* repair */,
                                            NULL, NULL, NULL, NULL, FALSE,
                                            pool));

      /* Move committed tmp/text-base to real text-base. */
      SVN_ERR (svn_wc__sync_text_base (filepath, pool));     
    }

  return SVN_NO_ERROR;
}


static void
signal_error (struct log_runner *loggy, svn_error_t *err)
{
  svn_xml_signal_bailout (svn_error_createf (SVN_ERR_WC_BAD_ADM_LOG,
                                             0,
                                             err,
                                             loggy->pool,
                                             "in directory %s",
                                             loggy->path->data),
                          loggy->parser);
}






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

static svn_error_t *
log_do_run_cmd (struct log_runner *loggy,
                const char *name,
                const XML_Char **atts)
{
  svn_error_t *err;
  apr_status_t apr_err;
  const char
    *infile_name,
    *outfile_name,
    *errfile_name;
  apr_file_t
    *infile = NULL,
    *outfile = NULL,
    *errfile = NULL;
  const char *args[10];
  
  args[0] = name;
  /* Grab the arguments.
     You want ugly?  I'll give you ugly... */
  args[1] = svn_xml_get_attr_value (SVN_WC__LOG_ATTR_ARG_1, atts);
  args[2] = svn_xml_get_attr_value (SVN_WC__LOG_ATTR_ARG_2, atts);
  args[3] = svn_xml_get_attr_value (SVN_WC__LOG_ATTR_ARG_3, atts);
  args[4] = svn_xml_get_attr_value (SVN_WC__LOG_ATTR_ARG_4, atts);
  args[5] = svn_xml_get_attr_value (SVN_WC__LOG_ATTR_ARG_5, atts);
  args[6] = svn_xml_get_attr_value (SVN_WC__LOG_ATTR_ARG_6, atts);
  args[7] = svn_xml_get_attr_value (SVN_WC__LOG_ATTR_ARG_7, atts);
  args[8] = svn_xml_get_attr_value (SVN_WC__LOG_ATTR_ARG_8, atts);
  args[9] = svn_xml_get_attr_value (SVN_WC__LOG_ATTR_ARG_9, atts);
  
  /* Grab the input and output, if any. */
  infile_name = svn_xml_get_attr_value (SVN_WC__LOG_ATTR_INFILE, atts);;
  outfile_name = svn_xml_get_attr_value (SVN_WC__LOG_ATTR_OUTFILE, atts);;
  errfile_name = svn_xml_get_attr_value (SVN_WC__LOG_ATTR_ERRFILE, atts);;
  
  if (infile_name)
    {
      svn_stringbuf_t *infile_path
        = svn_stringbuf_dup (loggy->path, loggy->pool);
      svn_path_add_component_nts (infile_path, infile_name,
                                  svn_path_local_style);
      
      apr_err = apr_file_open (&infile, infile_path->data, APR_READ,
                          APR_OS_DEFAULT, loggy->pool);
      if (apr_err)
        return svn_error_createf (apr_err, 0, NULL, loggy->pool,
                                  "error opening %s", infile_path->data);
    }
  
  if (outfile_name)
    {
      svn_stringbuf_t *outfile_path
        = svn_stringbuf_dup (loggy->path, loggy->pool);
      svn_path_add_component_nts (outfile_path, outfile_name,
                                  svn_path_local_style);
      
      /* kff todo: always creates and overwrites, currently.
         Could append if file exists... ?  Consider. */
      apr_err = apr_file_open (&outfile, outfile_path->data, 
                          (APR_WRITE | APR_CREATE),
                          APR_OS_DEFAULT, loggy->pool);
      if (apr_err)
        return svn_error_createf (apr_err, 0, NULL, loggy->pool,
                                  "error opening %s", outfile_path->data);
    }
  
  if (errfile_name)
    {
      svn_stringbuf_t *errfile_path
        = svn_stringbuf_dup (loggy->path, loggy->pool);
      svn_path_add_component_nts (errfile_path, errfile_name,
                                  svn_path_local_style);
      
      /* kff todo: always creates and overwrites, currently.
         Could append if file exists... ?  Consider. */
      apr_err = apr_file_open (&errfile, errfile_path->data, 
                          (APR_WRITE | APR_CREATE),
                          APR_OS_DEFAULT, loggy->pool);
      if (apr_err)
        return svn_error_createf (apr_err, 0, NULL, loggy->pool,
                                  "error opening %s", errfile_path->data);
    }
  
  err = svn_io_run_cmd (loggy->path->data, name, args, NULL, NULL,
                        infile, outfile, errfile, loggy->pool);
  if (err)
     return svn_error_createf (SVN_ERR_WC_BAD_ADM_LOG, 0, NULL, loggy->pool,
                               "error running %s in %s",
                               name, loggy->path->data);

  /* TODO: Handle status here, or pass it back to caller. */

  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 XML_Char **atts)
{
  svn_error_t *err;
  svn_boolean_t repair = FALSE;
  const char *dest = NULL, *eol_str = NULL, *revision = NULL;
  const char *date = NULL, *author = NULL, *url = 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 (SVN_ERR_WC_BAD_ADM_LOG, 0, NULL, loggy->pool,
                              "missing dest attr in %s", loggy->path->data);

  /* Optional: try to get any other translation-related attributes. */
  eol_str = svn_xml_get_attr_value (SVN_WC__LOG_ATTR_EOL_STR, atts);
  revision = svn_xml_get_attr_value (SVN_WC__LOG_ATTR_REVISION, atts);
  date = svn_xml_get_attr_value (SVN_WC__LOG_ATTR_DATE, atts);
  author = svn_xml_get_attr_value (SVN_WC__LOG_ATTR_AUTHOR, atts);
  url = svn_xml_get_attr_value (SVN_WC__LOG_ATTR_URL, atts);

  if (svn_xml_get_attr_value (SVN_WC__LOG_ATTR_REPAIR, atts))
    repair = TRUE;

  err = file_xfer_under_path (loggy->path, name, dest, action, 
                              eol_str, repair, revision, date, author, url,
                              loggy->pool);
  if (err)
    signal_error (loggy, err);

  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)
{
  apr_status_t apr_err;
  svn_stringbuf_t *full_path;

  full_path = svn_stringbuf_dup (loggy->path, loggy->pool);
  svn_path_add_component_nts (full_path, name, svn_path_local_style);

  apr_err = apr_file_remove (full_path->data, loggy->pool);
  if (apr_err)
    return svn_error_createf (apr_err, 0, NULL, loggy->pool,
                              "apr_file_remove couldn't remove %s", name);

  return SVN_NO_ERROR;
}


/* Remove file NAME in log's CWD iff it's zero bytes in size. */
static svn_error_t *
log_do_detect_conflict (struct log_runner *loggy,
                        const char *name,
                        const XML_Char **atts)                        
{
  svn_error_t *err;
  apr_status_t apr_err;
  apr_finfo_t finfo;
  svn_stringbuf_t *full_path;

  const char *rejfile =
    svn_xml_get_attr_value (SVN_WC_ENTRY_ATTR_REJFILE, atts);

  if (! rejfile)
    return 
      svn_error_createf (SVN_ERR_WC_BAD_ADM_LOG, 0, NULL, loggy->pool,
                         "log_do_detect_conflict: no text-rejfile attr in %s",
                         loggy->path->data);


  full_path = svn_stringbuf_dup (loggy->path, loggy->pool);
  svn_path_add_component_nts (full_path, rejfile, svn_path_local_style);

  apr_err = apr_stat (&finfo, full_path->data, APR_FINFO_MIN, loggy->pool);
  if (apr_err)
    return svn_error_createf (apr_err, 0, NULL, loggy->pool,
                              "log_do_detect_conflict: couldn't stat %s",
                              full_path->data);

  if (finfo.size == 0)
    {
      /* the `patch' program created an empty .rej file.  clean it
         up. */
      apr_err = apr_file_remove (full_path->data, loggy->pool);
      if (apr_err)
        return svn_error_createf (apr_err, 0, NULL, loggy->pool,
                                  "log_do_detect_conflict: couldn't rm %s", 
                                  name);
    }

  else 
    {
      /* size > 0, there must be an actual text conflict.   Mark the
         entry as conflicted! */
      apr_hash_t *atthash = svn_xml_make_att_hash (atts, loggy->pool);

      err = svn_wc__entry_modify
        (loggy->path,
         svn_stringbuf_create (name, loggy->pool),
         (SVN_WC__ENTRY_MODIFY_CONFLICTED | SVN_WC__ENTRY_MODIFY_ATTRIBUTES),
         SVN_INVALID_REVNUM,
         svn_node_none,
         svn_wc_schedule_normal,
         TRUE, FALSE,
         0,
         0,
         NULL,
         atthash, /* contains SVN_WC_ATTR_REJFILE */
         loggy->pool,
         NULL);
    }

  return SVN_NO_ERROR;
}




static svn_error_t *
log_do_modify_entry (struct log_runner *loggy,
                     const char *name,
                     const XML_Char **atts)
{
  svn_error_t *err;
  apr_hash_t *ah = svn_xml_make_att_hash (atts, loggy->pool);
  svn_stringbuf_t *sname = svn_stringbuf_create (name, loggy->pool);
  svn_stringbuf_t *tfile = svn_stringbuf_dup (loggy->path, loggy->pool);
  svn_wc_entry_t *entry;
  apr_uint16_t modify_flags;
  svn_stringbuf_t *valuestr;

  /* Convert the attributes into an entry structure. */
  SVN_ERR (svn_wc__atts_to_entry (&entry, &modify_flags, ah, loggy->pool));

  /* Did the log command give us any timestamps?  There are three
     possible scenarios here.  We must check both text_time
     and prop_time for each of the three scenarios.  */

  /* TEXT_TIME: */
  valuestr = apr_hash_get (ah, SVN_WC_ENTRY_ATTR_TEXT_TIME, 
                           APR_HASH_KEY_STRING);

  if ((modify_flags & SVN_WC__ENTRY_MODIFY_TEXT_TIME)
      && (! strcmp (valuestr->data, SVN_WC_TIMESTAMP_WC)))
    {
      enum svn_node_kind tfile_kind;
      apr_time_t text_time;

      if (strcmp (sname->data, SVN_WC_ENTRY_THIS_DIR))
        svn_path_add_component (tfile, sname, svn_path_local_style);
      
      err = svn_io_check_path (tfile, &tfile_kind, loggy->pool);
      if (err)
        return svn_error_createf
          (SVN_ERR_WC_BAD_ADM_LOG, 0, NULL, loggy->pool,
           "error checking path %s", tfile->data);
          
      err = svn_io_file_affected_time (&text_time, tfile, loggy->pool);
      if (err)
        return svn_error_createf
          (SVN_ERR_WC_BAD_ADM_LOG, 0, NULL, loggy->pool,
           "error getting file affected time on %s", tfile->data);

      entry->text_time = text_time;
    }

  /* PROP_TIME: */
  valuestr = apr_hash_get (ah, SVN_WC_ENTRY_ATTR_PROP_TIME, 
                           APR_HASH_KEY_STRING);

  if ((modify_flags & SVN_WC__ENTRY_MODIFY_PROP_TIME)
      && (! strcmp (valuestr->data, SVN_WC_TIMESTAMP_WC)))
    {
      svn_stringbuf_t *pfile;
      enum svn_node_kind pfile_kind;
      apr_time_t prop_time;

      err = svn_wc__prop_path (&pfile, tfile, 0, loggy->pool);
      if (err)
        signal_error (loggy, err);
      
      err = svn_io_check_path (pfile, &pfile_kind, loggy->pool);
      if (err)
        return svn_error_createf
          (SVN_ERR_WC_BAD_ADM_LOG, 0, NULL, loggy->pool,
           "error checking path %s", pfile->data);
      
      err = svn_io_file_affected_time (&prop_time, tfile, loggy->pool);
      if (err)
        return svn_error_createf
          (SVN_ERR_WC_BAD_ADM_LOG, 0, NULL, loggy->pool,
           "error getting file affected time on %s", pfile->data);

      entry->prop_time = prop_time;
    }

  /* Now write the new entry out */
  err = svn_wc__entry_modify (loggy->path,
                              sname,
                              modify_flags,
                              entry->revision,
                              entry->kind,
                              entry->schedule,
                              entry->conflicted,
                              entry->copied,
                              entry->text_time,
                              entry->prop_time,
                              entry->url,
                              entry->attributes,
                              loggy->pool,
                              NULL);

  if (err)
    return svn_error_createf (SVN_ERR_WC_BAD_ADM_LOG, 0, NULL, loggy->pool,
                              "error merge_syncing entry %s", name);

  return SVN_NO_ERROR;
}


/* <NAME>:  this log command is (at the moment) only executed by the
   update editor.  It attempts to forcefully remove working data. */
static svn_error_t *
log_do_delete_entry (struct log_runner *loggy, const char *name)
{
  svn_wc_entry_t *entry;
  svn_error_t *err = NULL;
  svn_stringbuf_t *sname = svn_stringbuf_create (name, loggy->pool);
  svn_stringbuf_t *full_path = svn_stringbuf_dup (loggy->path, loggy->pool);
  svn_stringbuf_t *this_dir = svn_stringbuf_create (SVN_WC_ENTRY_THIS_DIR,
                                              loggy->pool);

  /* Figure out if 'name' is a dir or a file */
  svn_path_add_component (full_path, sname, svn_path_local_style);
  svn_wc_entry (&entry, full_path, loggy->pool);

  if (! entry)
    /* Hmm....this entry is already absent from the revision control
       system.  Chances are good that this item was removed via a
       commit from this working copy.  */
    return SVN_NO_ERROR;

  /* Remove the object from revision control -- whether it's a
     single file or recursive directory removal.  Attempt
     attempt to destroy all working files & dirs too. */
  if (entry->kind == svn_node_dir)
    err = svn_wc_remove_from_revision_control (full_path, this_dir,
                                               TRUE, loggy->pool);
  else if (entry->kind == svn_node_file)
    err = svn_wc_remove_from_revision_control (loggy->path, sname,
                                               TRUE, loggy->pool);
    
  /* It's possible that locally modified files were left behind during
     the removal.  That's okay;  just check for this special case. */
  if (err && (err->apr_err != SVN_ERR_WC_LEFT_LOCAL_MOD))
    return err;

  /* (## Perhaps someday have the client print a warning that "locally
     modified files were not deleted" ??) */    

  return SVN_NO_ERROR;
}


/* Note:  assuming that svn_wc__log_commit() is what created all of
   the <committed...> commands, the `name' attribute will either be a
   file or SVN_WC_ENTRY_THIS_DIR. */
static svn_error_t *
log_do_committed (struct log_runner *loggy,
                  const char *name,
                  const XML_Char **atts)
{
  svn_error_t *err;
  const char *revstr
    = svn_xml_get_attr_value (SVN_WC__LOG_ATTR_REVISION, atts);

  if (! revstr)
    return svn_error_createf (SVN_ERR_WC_BAD_ADM_LOG, 0, NULL, loggy->pool,
                              "missing revision attr for %s", name);
  else
    {
      svn_stringbuf_t *working_file = NULL;
      svn_stringbuf_t *tmp_base;
      apr_time_t text_time = 0; /* By default, don't override old stamp. */
      apr_time_t prop_time = 0; /* By default, don't override old stamp. */
      enum svn_node_kind kind;
      svn_wc_entry_t *entry;
      svn_stringbuf_t *prop_path, *tmp_prop_path, *prop_base_path;
      svn_stringbuf_t *sname = svn_stringbuf_create (name, loggy->pool);
      svn_boolean_t is_this_dir;

      /* `name' is either a file's basename, or SVN_WC_ENTRY_THIS_DIR. */
      is_this_dir = (strcmp (name, SVN_WC_ENTRY_THIS_DIR)) ? FALSE : TRUE;
      
      /* Determine the actual full path of the affected item so we can
         easily read its entry and check its state. */
      {
        svn_stringbuf_t *full_path;

        full_path = svn_stringbuf_dup (loggy->path, loggy->pool);
        if (! is_this_dir)
          svn_path_add_component (full_path, sname, svn_path_local_style);
        SVN_ERR (svn_wc_entry (&entry, full_path, loggy->pool));
        if ((! is_this_dir) 
            && (entry->kind != svn_node_file))
          return svn_error_createf 
            (SVN_ERR_WC_BAD_ADM_LOG, 0, NULL, loggy->pool,
             "log command for directory '%s' mislocated", name);
      }

      /* What to do if the committed entry was scheduled for deletion: */
      if (entry && (entry->schedule == svn_wc_schedule_delete))
        {
          if (is_this_dir)
            /* Drop a 'killme' file into my own adminstrative dir;
               this signals the svn_wc__run_log() to blow away .svn/
               after its done with this logfile.  */
            SVN_ERR (svn_wc__make_adm_thing (loggy->path,
                                             SVN_WC__ADM_KILLME,
                                             svn_node_file, 
                                             APR_OS_DEFAULT,
                                             0,
                                             loggy->pool));
          else
            /* We can safely remove files from revision control
               without screwing something else up. */
            SVN_ERR (svn_wc_remove_from_revision_control
                     (loggy->path, sname, FALSE, loggy->pool));
        }
               
      else   /* entry not deleted, so mark commited-to-date */
        {
          svn_stringbuf_t *pdir, *basename;
          apr_hash_t *entries;

          if ((entry && (entry->schedule == svn_wc_schedule_replace))
              && is_this_dir)
            {
              apr_hash_index_t *hi;
              
              /* If THIS_DIR has been replaced, all its immmediate
                 children *must* be either marked as {D, A, or R}.
                 Children which are A or R will be reported as individual
                 commit-targets, and thus will be re-visited by
                 log_do_committed().  Children which are marked as D,
                 however, need to be outright removed from revision
                 control.  */
              
              /* Loop over all children entries, look for D markers. */
              SVN_ERR (svn_wc_entries_read (&entries, loggy->path,
                                            loggy->pool));
              
              for (hi = apr_hash_first (loggy->pool, entries); 
                   hi; 
                   hi = apr_hash_next (hi))
                {
                  const void *key;
                  const char *keystring;
                  apr_ssize_t klen;
                  void *val;
                  svn_stringbuf_t *current_entry_name;
                  svn_wc_entry_t *current_entry; 
                  
                  /* Get the next entry */
                  apr_hash_this (hi, &key, &klen, &val);
                  keystring = (const char *) key;
                  current_entry = (svn_wc_entry_t *) val;
                  
                  /* Skip each entry that isn't scheduled for deletion. */
                  if (current_entry->schedule != svn_wc_schedule_delete)
                    continue;
                  
                  /* Get the name of entry, remove from revision control. */
                  current_entry_name = svn_stringbuf_create (keystring,
                                                             loggy->pool);
                  
                  if (current_entry->kind == svn_node_file)
                    SVN_ERR (svn_wc_remove_from_revision_control 
                             (loggy->path,
                              current_entry_name,
                              FALSE, loggy->pool));
                  
                  else if (current_entry->kind == svn_node_dir)
                    {
                      svn_stringbuf_t *parent = svn_stringbuf_dup
                        (loggy->path, loggy->pool);
                      svn_stringbuf_t *thisdir =
                        svn_stringbuf_create (SVN_WC_ENTRY_THIS_DIR, 
                                              loggy->pool);
                      svn_path_add_component (parent, current_entry_name,
                                              svn_path_local_style);
                      SVN_ERR (svn_wc_remove_from_revision_control
                               (parent, thisdir, FALSE, loggy->pool));
                    }
                }
            }

          if (! is_this_dir)
            {
              /* If we get here, `name' is a file's basename.
                 `basename' is an svn_stringbuf_t version of it.  Check
                 for textual changes. */
              working_file = svn_stringbuf_dup (loggy->path, loggy->pool);
              svn_path_add_component (working_file,
                                      sname,
                                      svn_path_local_style);
              tmp_base = svn_wc__text_base_path (working_file, 1, loggy->pool);
              
              err = svn_io_check_path (tmp_base, &kind, loggy->pool);
              if (err)
                return svn_error_createf
                  (SVN_ERR_WC_BAD_ADM_LOG, 0, NULL, loggy->pool,
                   "error checking existence of %s", name);
              
              if (kind == svn_node_file)
                {
                  svn_boolean_t same;

                  svn_wc__versioned_file_modcheck (&same,
                                                   working_file,
                                                   tmp_base,
                                                   loggy->pool);
                  if (err)
                    return svn_error_createf 
                      (SVN_ERR_WC_BAD_ADM_LOG, 0, NULL, loggy->pool,
                       "error comparing %s and %s",
                       working_file->data, tmp_base->data);
                  
                  /* What's going on here: the working copy has been
                     copied to tmp/text-base/ during the commit.  That's<commit_msg>(log_do_committed): make sure to assign to 'err' before testing it.<commit_after>/*
 * log.c:  handle the adm area's log file.
 *
 * ====================================================================
 * Copyright (c) 2000-2001 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 "wc.h"




/*** Userdata for the callbacks. ***/
struct log_runner
{
  apr_pool_t *pool;
  svn_xml_parser_t *parser;
  svn_stringbuf_t *path;  /* the dir in which this is all happening */
};




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

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



/* Copy (or rename, if RENAME is non-zero) NAME to DEST, assuming that
   PATH is the common parent of both locations. 

   If ACTION is 'cp', then do translation IFF any of EOL_STR,
   REVISION, DATE, AUTHOR, or URL are non-NULL.  When doing
   translation, setting REPAIR indicates that inconsistent line
   endings in SRC are translated to EOL_STR; else error is returned.
   (For more info, read the docstring of svn_io_copy_and_translate).
*/
static svn_error_t *
file_xfer_under_path (svn_stringbuf_t *path,
                      const char *name,
                      const char *dest,
                      enum svn_wc__xfer_action action,
                      const char *eol_str,
                      svn_boolean_t repair,
                      const char *revision,
                      const char *date,
                      const char *author,
                      const char *url,                      
                      apr_pool_t *pool)
{
  apr_status_t status;
  svn_stringbuf_t *full_from_path, *full_dest_path;

  full_from_path = svn_stringbuf_dup (path, pool);
  full_dest_path = svn_stringbuf_dup (path, pool);

  svn_path_add_component_nts (full_from_path, name, svn_path_local_style);
  svn_path_add_component_nts (full_dest_path, dest, svn_path_local_style);

  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_and_translate (full_from_path->data,
                                        full_dest_path->data,
                                        eol_str,
                                        repair,
                                        revision,
                                        date,
                                        author,
                                        url,
                                        TRUE, /* ### todo: expand? */
                                        pool);

    case svn_wc__xfer_mv:
      status = apr_file_rename (full_from_path->data,
                              full_dest_path->data, pool);
      if (status)
        return svn_error_createf (status, 0, NULL, pool,
                                  "file_xfer_under_path: "
                                  "can't move %s to %s",
                                  name, dest);
  }

  return SVN_NO_ERROR;
}



static svn_error_t *
replace_text_base (svn_stringbuf_t *path,
                   const char *name,
                   apr_pool_t *pool)
{
  svn_stringbuf_t *filepath;
  svn_stringbuf_t *tmp_text_base;
  svn_error_t *err;
  enum svn_node_kind kind;

  filepath = svn_stringbuf_dup (path, pool);
  svn_path_add_component_nts (filepath, name, svn_path_local_style);

  tmp_text_base = svn_wc__text_base_path (filepath, 1, pool);
  err = svn_io_check_path (tmp_text_base, &kind, pool);
  if (err)
    return err;

  if (kind == svn_node_none)
    return SVN_NO_ERROR;  /* tolerate mop-up calls gracefully */
  else
    {
      enum svn_wc__eol_style eol_style;
      const char *eol_str;

      SVN_ERR (svn_wc__get_eol_style (&eol_style, &eol_str,
                                      filepath->data, pool));

      /* If the eol-style is 'fixed', then the committed
         tmp/text-base/file need to be copied back out on top of the
         working file in the correct 'fixed' eol style.  We do this
         because it may have been repaired during commit, and we want
         the working file to look exactly like text-base after a
         commit. */
      if (eol_style == svn_wc__eol_style_fixed)
        SVN_ERR (svn_io_copy_and_translate (tmp_text_base->data,
                                            filepath->data,
                                            eol_str, TRUE /* repair */,
                                            NULL, NULL, NULL, NULL, FALSE,
                                            pool));

      /* Move committed tmp/text-base to real text-base. */
      SVN_ERR (svn_wc__sync_text_base (filepath, pool));     
    }

  return SVN_NO_ERROR;
}


static void
signal_error (struct log_runner *loggy, svn_error_t *err)
{
  svn_xml_signal_bailout (svn_error_createf (SVN_ERR_WC_BAD_ADM_LOG,
                                             0,
                                             err,
                                             loggy->pool,
                                             "in directory %s",
                                             loggy->path->data),
                          loggy->parser);
}






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

static svn_error_t *
log_do_run_cmd (struct log_runner *loggy,
                const char *name,
                const XML_Char **atts)
{
  svn_error_t *err;
  apr_status_t apr_err;
  const char
    *infile_name,
    *outfile_name,
    *errfile_name;
  apr_file_t
    *infile = NULL,
    *outfile = NULL,
    *errfile = NULL;
  const char *args[10];
  
  args[0] = name;
  /* Grab the arguments.
     You want ugly?  I'll give you ugly... */
  args[1] = svn_xml_get_attr_value (SVN_WC__LOG_ATTR_ARG_1, atts);
  args[2] = svn_xml_get_attr_value (SVN_WC__LOG_ATTR_ARG_2, atts);
  args[3] = svn_xml_get_attr_value (SVN_WC__LOG_ATTR_ARG_3, atts);
  args[4] = svn_xml_get_attr_value (SVN_WC__LOG_ATTR_ARG_4, atts);
  args[5] = svn_xml_get_attr_value (SVN_WC__LOG_ATTR_ARG_5, atts);
  args[6] = svn_xml_get_attr_value (SVN_WC__LOG_ATTR_ARG_6, atts);
  args[7] = svn_xml_get_attr_value (SVN_WC__LOG_ATTR_ARG_7, atts);
  args[8] = svn_xml_get_attr_value (SVN_WC__LOG_ATTR_ARG_8, atts);
  args[9] = svn_xml_get_attr_value (SVN_WC__LOG_ATTR_ARG_9, atts);
  
  /* Grab the input and output, if any. */
  infile_name = svn_xml_get_attr_value (SVN_WC__LOG_ATTR_INFILE, atts);;
  outfile_name = svn_xml_get_attr_value (SVN_WC__LOG_ATTR_OUTFILE, atts);;
  errfile_name = svn_xml_get_attr_value (SVN_WC__LOG_ATTR_ERRFILE, atts);;
  
  if (infile_name)
    {
      svn_stringbuf_t *infile_path
        = svn_stringbuf_dup (loggy->path, loggy->pool);
      svn_path_add_component_nts (infile_path, infile_name,
                                  svn_path_local_style);
      
      apr_err = apr_file_open (&infile, infile_path->data, APR_READ,
                          APR_OS_DEFAULT, loggy->pool);
      if (apr_err)
        return svn_error_createf (apr_err, 0, NULL, loggy->pool,
                                  "error opening %s", infile_path->data);
    }
  
  if (outfile_name)
    {
      svn_stringbuf_t *outfile_path
        = svn_stringbuf_dup (loggy->path, loggy->pool);
      svn_path_add_component_nts (outfile_path, outfile_name,
                                  svn_path_local_style);
      
      /* kff todo: always creates and overwrites, currently.
         Could append if file exists... ?  Consider. */
      apr_err = apr_file_open (&outfile, outfile_path->data, 
                          (APR_WRITE | APR_CREATE),
                          APR_OS_DEFAULT, loggy->pool);
      if (apr_err)
        return svn_error_createf (apr_err, 0, NULL, loggy->pool,
                                  "error opening %s", outfile_path->data);
    }
  
  if (errfile_name)
    {
      svn_stringbuf_t *errfile_path
        = svn_stringbuf_dup (loggy->path, loggy->pool);
      svn_path_add_component_nts (errfile_path, errfile_name,
                                  svn_path_local_style);
      
      /* kff todo: always creates and overwrites, currently.
         Could append if file exists... ?  Consider. */
      apr_err = apr_file_open (&errfile, errfile_path->data, 
                          (APR_WRITE | APR_CREATE),
                          APR_OS_DEFAULT, loggy->pool);
      if (apr_err)
        return svn_error_createf (apr_err, 0, NULL, loggy->pool,
                                  "error opening %s", errfile_path->data);
    }
  
  err = svn_io_run_cmd (loggy->path->data, name, args, NULL, NULL,
                        infile, outfile, errfile, loggy->pool);
  if (err)
     return svn_error_createf (SVN_ERR_WC_BAD_ADM_LOG, 0, NULL, loggy->pool,
                               "error running %s in %s",
                               name, loggy->path->data);

  /* TODO: Handle status here, or pass it back to caller. */

  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 XML_Char **atts)
{
  svn_error_t *err;
  svn_boolean_t repair = FALSE;
  const char *dest = NULL, *eol_str = NULL, *revision = NULL;
  const char *date = NULL, *author = NULL, *url = 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 (SVN_ERR_WC_BAD_ADM_LOG, 0, NULL, loggy->pool,
                              "missing dest attr in %s", loggy->path->data);

  /* Optional: try to get any other translation-related attributes. */
  eol_str = svn_xml_get_attr_value (SVN_WC__LOG_ATTR_EOL_STR, atts);
  revision = svn_xml_get_attr_value (SVN_WC__LOG_ATTR_REVISION, atts);
  date = svn_xml_get_attr_value (SVN_WC__LOG_ATTR_DATE, atts);
  author = svn_xml_get_attr_value (SVN_WC__LOG_ATTR_AUTHOR, atts);
  url = svn_xml_get_attr_value (SVN_WC__LOG_ATTR_URL, atts);

  if (svn_xml_get_attr_value (SVN_WC__LOG_ATTR_REPAIR, atts))
    repair = TRUE;

  err = file_xfer_under_path (loggy->path, name, dest, action, 
                              eol_str, repair, revision, date, author, url,
                              loggy->pool);
  if (err)
    signal_error (loggy, err);

  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)
{
  apr_status_t apr_err;
  svn_stringbuf_t *full_path;

  full_path = svn_stringbuf_dup (loggy->path, loggy->pool);
  svn_path_add_component_nts (full_path, name, svn_path_local_style);

  apr_err = apr_file_remove (full_path->data, loggy->pool);
  if (apr_err)
    return svn_error_createf (apr_err, 0, NULL, loggy->pool,
                              "apr_file_remove couldn't remove %s", name);

  return SVN_NO_ERROR;
}


/* Remove file NAME in log's CWD iff it's zero bytes in size. */
static svn_error_t *
log_do_detect_conflict (struct log_runner *loggy,
                        const char *name,
                        const XML_Char **atts)                        
{
  svn_error_t *err;
  apr_status_t apr_err;
  apr_finfo_t finfo;
  svn_stringbuf_t *full_path;

  const char *rejfile =
    svn_xml_get_attr_value (SVN_WC_ENTRY_ATTR_REJFILE, atts);

  if (! rejfile)
    return 
      svn_error_createf (SVN_ERR_WC_BAD_ADM_LOG, 0, NULL, loggy->pool,
                         "log_do_detect_conflict: no text-rejfile attr in %s",
                         loggy->path->data);


  full_path = svn_stringbuf_dup (loggy->path, loggy->pool);
  svn_path_add_component_nts (full_path, rejfile, svn_path_local_style);

  apr_err = apr_stat (&finfo, full_path->data, APR_FINFO_MIN, loggy->pool);
  if (apr_err)
    return svn_error_createf (apr_err, 0, NULL, loggy->pool,
                              "log_do_detect_conflict: couldn't stat %s",
                              full_path->data);

  if (finfo.size == 0)
    {
      /* the `patch' program created an empty .rej file.  clean it
         up. */
      apr_err = apr_file_remove (full_path->data, loggy->pool);
      if (apr_err)
        return svn_error_createf (apr_err, 0, NULL, loggy->pool,
                                  "log_do_detect_conflict: couldn't rm %s", 
                                  name);
    }

  else 
    {
      /* size > 0, there must be an actual text conflict.   Mark the
         entry as conflicted! */
      apr_hash_t *atthash = svn_xml_make_att_hash (atts, loggy->pool);

      err = svn_wc__entry_modify
        (loggy->path,
         svn_stringbuf_create (name, loggy->pool),
         (SVN_WC__ENTRY_MODIFY_CONFLICTED | SVN_WC__ENTRY_MODIFY_ATTRIBUTES),
         SVN_INVALID_REVNUM,
         svn_node_none,
         svn_wc_schedule_normal,
         TRUE, FALSE,
         0,
         0,
         NULL,
         atthash, /* contains SVN_WC_ATTR_REJFILE */
         loggy->pool,
         NULL);
    }

  return SVN_NO_ERROR;
}




static svn_error_t *
log_do_modify_entry (struct log_runner *loggy,
                     const char *name,
                     const XML_Char **atts)
{
  svn_error_t *err;
  apr_hash_t *ah = svn_xml_make_att_hash (atts, loggy->pool);
  svn_stringbuf_t *sname = svn_stringbuf_create (name, loggy->pool);
  svn_stringbuf_t *tfile = svn_stringbuf_dup (loggy->path, loggy->pool);
  svn_wc_entry_t *entry;
  apr_uint16_t modify_flags;
  svn_stringbuf_t *valuestr;

  /* Convert the attributes into an entry structure. */
  SVN_ERR (svn_wc__atts_to_entry (&entry, &modify_flags, ah, loggy->pool));

  /* Did the log command give us any timestamps?  There are three
     possible scenarios here.  We must check both text_time
     and prop_time for each of the three scenarios.  */

  /* TEXT_TIME: */
  valuestr = apr_hash_get (ah, SVN_WC_ENTRY_ATTR_TEXT_TIME, 
                           APR_HASH_KEY_STRING);

  if ((modify_flags & SVN_WC__ENTRY_MODIFY_TEXT_TIME)
      && (! strcmp (valuestr->data, SVN_WC_TIMESTAMP_WC)))
    {
      enum svn_node_kind tfile_kind;
      apr_time_t text_time;

      if (strcmp (sname->data, SVN_WC_ENTRY_THIS_DIR))
        svn_path_add_component (tfile, sname, svn_path_local_style);
      
      err = svn_io_check_path (tfile, &tfile_kind, loggy->pool);
      if (err)
        return svn_error_createf
          (SVN_ERR_WC_BAD_ADM_LOG, 0, NULL, loggy->pool,
           "error checking path %s", tfile->data);
          
      err = svn_io_file_affected_time (&text_time, tfile, loggy->pool);
      if (err)
        return svn_error_createf
          (SVN_ERR_WC_BAD_ADM_LOG, 0, NULL, loggy->pool,
           "error getting file affected time on %s", tfile->data);

      entry->text_time = text_time;
    }

  /* PROP_TIME: */
  valuestr = apr_hash_get (ah, SVN_WC_ENTRY_ATTR_PROP_TIME, 
                           APR_HASH_KEY_STRING);

  if ((modify_flags & SVN_WC__ENTRY_MODIFY_PROP_TIME)
      && (! strcmp (valuestr->data, SVN_WC_TIMESTAMP_WC)))
    {
      svn_stringbuf_t *pfile;
      enum svn_node_kind pfile_kind;
      apr_time_t prop_time;

      err = svn_wc__prop_path (&pfile, tfile, 0, loggy->pool);
      if (err)
        signal_error (loggy, err);
      
      err = svn_io_check_path (pfile, &pfile_kind, loggy->pool);
      if (err)
        return svn_error_createf
          (SVN_ERR_WC_BAD_ADM_LOG, 0, NULL, loggy->pool,
           "error checking path %s", pfile->data);
      
      err = svn_io_file_affected_time (&prop_time, tfile, loggy->pool);
      if (err)
        return svn_error_createf
          (SVN_ERR_WC_BAD_ADM_LOG, 0, NULL, loggy->pool,
           "error getting file affected time on %s", pfile->data);

      entry->prop_time = prop_time;
    }

  /* Now write the new entry out */
  err = svn_wc__entry_modify (loggy->path,
                              sname,
                              modify_flags,
                              entry->revision,
                              entry->kind,
                              entry->schedule,
                              entry->conflicted,
                              entry->copied,
                              entry->text_time,
                              entry->prop_time,
                              entry->url,
                              entry->attributes,
                              loggy->pool,
                              NULL);

  if (err)
    return svn_error_createf (SVN_ERR_WC_BAD_ADM_LOG, 0, NULL, loggy->pool,
                              "error merge_syncing entry %s", name);

  return SVN_NO_ERROR;
}


/* <NAME>:  this log command is (at the moment) only executed by the
   update editor.  It attempts to forcefully remove working data. */
static svn_error_t *
log_do_delete_entry (struct log_runner *loggy, const char *name)
{
  svn_wc_entry_t *entry;
  svn_error_t *err = NULL;
  svn_stringbuf_t *sname = svn_stringbuf_create (name, loggy->pool);
  svn_stringbuf_t *full_path = svn_stringbuf_dup (loggy->path, loggy->pool);
  svn_stringbuf_t *this_dir = svn_stringbuf_create (SVN_WC_ENTRY_THIS_DIR,
                                              loggy->pool);

  /* Figure out if 'name' is a dir or a file */
  svn_path_add_component (full_path, sname, svn_path_local_style);
  svn_wc_entry (&entry, full_path, loggy->pool);

  if (! entry)
    /* Hmm....this entry is already absent from the revision control
       system.  Chances are good that this item was removed via a
       commit from this working copy.  */
    return SVN_NO_ERROR;

  /* Remove the object from revision control -- whether it's a
     single file or recursive directory removal.  Attempt
     attempt to destroy all working files & dirs too. */
  if (entry->kind == svn_node_dir)
    err = svn_wc_remove_from_revision_control (full_path, this_dir,
                                               TRUE, loggy->pool);
  else if (entry->kind == svn_node_file)
    err = svn_wc_remove_from_revision_control (loggy->path, sname,
                                               TRUE, loggy->pool);
    
  /* It's possible that locally modified files were left behind during
     the removal.  That's okay;  just check for this special case. */
  if (err && (err->apr_err != SVN_ERR_WC_LEFT_LOCAL_MOD))
    return err;

  /* (## Perhaps someday have the client print a warning that "locally
     modified files were not deleted" ??) */    

  return SVN_NO_ERROR;
}


/* Note:  assuming that svn_wc__log_commit() is what created all of
   the <committed...> commands, the `name' attribute will either be a
   file or SVN_WC_ENTRY_THIS_DIR. */
static svn_error_t *
log_do_committed (struct log_runner *loggy,
                  const char *name,
                  const XML_Char **atts)
{
  svn_error_t *err;
  const char *revstr
    = svn_xml_get_attr_value (SVN_WC__LOG_ATTR_REVISION, atts);

  if (! revstr)
    return svn_error_createf (SVN_ERR_WC_BAD_ADM_LOG, 0, NULL, loggy->pool,
                              "missing revision attr for %s", name);
  else
    {
      svn_stringbuf_t *working_file = NULL;
      svn_stringbuf_t *tmp_base;
      apr_time_t text_time = 0; /* By default, don't override old stamp. */
      apr_time_t prop_time = 0; /* By default, don't override old stamp. */
      enum svn_node_kind kind;
      svn_wc_entry_t *entry;
      svn_stringbuf_t *prop_path, *tmp_prop_path, *prop_base_path;
      svn_stringbuf_t *sname = svn_stringbuf_create (name, loggy->pool);
      svn_boolean_t is_this_dir;

      /* `name' is either a file's basename, or SVN_WC_ENTRY_THIS_DIR. */
      is_this_dir = (strcmp (name, SVN_WC_ENTRY_THIS_DIR)) ? FALSE : TRUE;
      
      /* Determine the actual full path of the affected item so we can
         easily read its entry and check its state. */
      {
        svn_stringbuf_t *full_path;

        full_path = svn_stringbuf_dup (loggy->path, loggy->pool);
        if (! is_this_dir)
          svn_path_add_component (full_path, sname, svn_path_local_style);
        SVN_ERR (svn_wc_entry (&entry, full_path, loggy->pool));
        if ((! is_this_dir) 
            && (entry->kind != svn_node_file))
          return svn_error_createf 
            (SVN_ERR_WC_BAD_ADM_LOG, 0, NULL, loggy->pool,
             "log command for directory '%s' mislocated", name);
      }

      /* What to do if the committed entry was scheduled for deletion: */
      if (entry && (entry->schedule == svn_wc_schedule_delete))
        {
          if (is_this_dir)
            /* Drop a 'killme' file into my own adminstrative dir;
               this signals the svn_wc__run_log() to blow away .svn/
               after its done with this logfile.  */
            SVN_ERR (svn_wc__make_adm_thing (loggy->path,
                                             SVN_WC__ADM_KILLME,
                                             svn_node_file, 
                                             APR_OS_DEFAULT,
                                             0,
                                             loggy->pool));
          else
            /* We can safely remove files from revision control
               without screwing something else up. */
            SVN_ERR (svn_wc_remove_from_revision_control
                     (loggy->path, sname, FALSE, loggy->pool));
        }
               
      else   /* entry not deleted, so mark commited-to-date */
        {
          svn_stringbuf_t *pdir, *basename;
          apr_hash_t *entries;

          if ((entry && (entry->schedule == svn_wc_schedule_replace))
              && is_this_dir)
            {
              apr_hash_index_t *hi;
              
              /* If THIS_DIR has been replaced, all its immmediate
                 children *must* be either marked as {D, A, or R}.
                 Children which are A or R will be reported as individual
                 commit-targets, and thus will be re-visited by
                 log_do_committed().  Children which are marked as D,
                 however, need to be outright removed from revision
                 control.  */
              
              /* Loop over all children entries, look for D markers. */
              SVN_ERR (svn_wc_entries_read (&entries, loggy->path,
                                            loggy->pool));
              
              for (hi = apr_hash_first (loggy->pool, entries); 
                   hi; 
                   hi = apr_hash_next (hi))
                {
                  const void *key;
                  const char *keystring;
                  apr_ssize_t klen;
                  void *val;
                  svn_stringbuf_t *current_entry_name;
                  svn_wc_entry_t *current_entry; 
                  
                  /* Get the next entry */
                  apr_hash_this (hi, &key, &klen, &val);
                  keystring = (const char *) key;
                  current_entry = (svn_wc_entry_t *) val;
                  
                  /* Skip each entry that isn't scheduled for deletion. */
                  if (current_entry->schedule != svn_wc_schedule_delete)
                    continue;
                  
                  /* Get the name of entry, remove from revision control. */
                  current_entry_name = svn_stringbuf_create (keystring,
                                                             loggy->pool);
                  
                  if (current_entry->kind == svn_node_file)
                    SVN_ERR (svn_wc_remove_from_revision_control 
                             (loggy->path,
                              current_entry_name,
                              FALSE, loggy->pool));
                  
                  else if (current_entry->kind == svn_node_dir)
                    {
                      svn_stringbuf_t *parent = svn_stringbuf_dup
                        (loggy->path, loggy->pool);
                      svn_stringbuf_t *thisdir =
                        svn_stringbuf_create (SVN_WC_ENTRY_THIS_DIR, 
                                              loggy->pool);
                      svn_path_add_component (parent, current_entry_name,
                                              svn_path_local_style);
                      SVN_ERR (svn_wc_remove_from_revision_control
                               (parent, thisdir, FALSE, loggy->pool));
                    }
                }
            }

          if (! is_this_dir)
            {
              /* If we get here, `name' is a file's basename.
                 `basename' is an svn_stringbuf_t version of it.  Check
                 for textual changes. */
              working_file = svn_stringbuf_dup (loggy->path, loggy->pool);
              svn_path_add_component (working_file,
                                      sname,
                                      svn_path_local_style);
              tmp_base = svn_wc__text_base_path (working_file, 1, loggy->pool);
              
              err = svn_io_check_path (tmp_base, &kind, loggy->pool);
              if (err)
                return svn_error_createf
                  (SVN_ERR_WC_BAD_ADM_LOG, 0, NULL, loggy->pool,
                   "error checking existence of %s", name);
              
              if (kind == svn_node_file)
                {
                  svn_boolean_t same;

                  err = svn_wc__versioned_file_modcheck (&same,
                                                         working_file,
                                                         tmp_base,
                                                         loggy->pool);
                  if (err)
                    return svn_error_createf 
                      (SVN_ERR_WC_BAD_ADM_LOG, 0, NULL, loggy->pool,
                       "error comparing %s and %s",
                       working_file->data, tmp_base->data);
                  
                  /* What's going on here: the working copy has been
                     copied to tmp/text-base/ during the commit.  That's
{'fraction_non_alphanumeric': 0.08841303205465055, 'fraction_numerical': 0.0023271526161699574, 'mean_word_length': 1.7811994362962575, 'pattern_counts': {'":': 0, '<': 15, '<?xml version=': 0, '>': 301, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 54}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 142, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '5870488', 'n_tokens_mistral': 17074, 'n_tokens_neox': 16555, 'n_words': 3979}