<commit_before>/*
 * diff_editor.c -- The diff editor for comparing the working copy against the
 *                  repository.
 *
 * ====================================================================
 *    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.
 * ====================================================================
 */

/*
 * This code uses an svn_delta_editor_t editor driven by
 * svn_wc_crawl_revisions (like the update command) to retrieve the
 * differences between the working copy and the requested repository
 * version. Rather than updating the working copy, this new editor creates
 * temporary files that contain the pristine repository versions. When the
 * crawler closes the files the editor calls back to a client layer
 * function to compare the working copy and the temporary file. There is
 * only ever one temporary file in existence at any time.
 *
 * When the crawler closes a directory, the editor then calls back to the
 * client layer to compare any remaining files that may have been modified
 * locally. Added directories do not have corresponding temporary
 * directories created, as they are not needed.
 *
 * The diff result from this editor is a combination of the restructuring
 * operations from the repository with the local restructurings since checking
 * out.
 *
 * ### TODO: Make sure that we properly support and report multi layered
 *           operations instead of only simple file replacements.
 *
 * ### TODO: Replacements where the node kind changes needs support. It
 * mostly works when the change is in the repository, but not when it is
 * in the working copy.
 *
 * ### TODO: Do we need to support copyfrom?
 *
 */

#include <apr_hash.h>
#include <apr_md5.h>

#include <assert.h>

#include "svn_error.h"
#include "svn_pools.h"
#include "svn_dirent_uri.h"
#include "svn_path.h"
#include "svn_hash.h"

#include "private/svn_subr_private.h"
#include "private/svn_wc_private.h"
#include "private/svn_diff_tree.h"
#include "private/svn_editor.h"

#include "wc.h"
#include "props.h"
#include "adm_files.h"
#include "translate.h"

#include "svn_private_config.h"

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



/* Overall crawler editor baton.
 */
struct edit_baton_t 
{
  /* A wc db. */
  svn_wc__db_t *db;

  /* A diff tree processor, receiving the result of the diff. */
  const svn_diff_tree_processor_t *processor;

  /* A boolean indicating whether local additions should be reported before
     remote deletes. The processor can transform adds in deletes and deletes
     in adds, but it can't reorder the output. */
  svn_boolean_t local_before_remote;

  /* ANCHOR/TARGET represent the base of the hierarchy to be compared. */
  const char *target;
  const char *anchor_abspath;

  /* Target revision */
  svn_revnum_t revnum;

  /* Was the root opened? */
  svn_boolean_t root_opened;

  /* How does this diff descend as seen from target? */
  svn_depth_t depth;

  /* Should this diff ignore node ancestry? */
  svn_boolean_t ignore_ancestry;

  /* Should this diff not compare copied files with their source? */
  svn_boolean_t show_copies_as_adds;

  /* Possibly diff repos against text-bases instead of working files. */
  svn_boolean_t diff_pristine;

  /* Hash whose keys are const char * changelist names. */
  apr_hash_t *changelist_hash;

  /* Cancel function/baton */
  svn_cancel_func_t cancel_func;
  void *cancel_baton;

  apr_pool_t *pool;
};

/* Directory level baton.
 */
struct dir_baton_t
{
  /* Reference to parent directory baton (or NULL for the root) */
  struct dir_baton_t *parent_baton;

  /* The depth at which this directory should be diffed. */
  svn_depth_t depth;

  /* The name and path of this directory as if they would be/are in the
      local working copy. */
  const char *name;
  const char *relpath;
  const char *local_abspath;

  /* TRUE if the file is added by the editor drive. */
  svn_boolean_t added;
  /* TRUE if the node exists only on the repository side (op_depth 0 or added) */
  svn_boolean_t repos_only;
  /* TRUE if the node is to be compared with an unrelated node*/
  svn_boolean_t ignoring_ancestry;

  /* Processor state */
  void *pdb;
  svn_boolean_t skip;
  svn_boolean_t skip_children;

  svn_diff_source_t *left_src;
  svn_diff_source_t *right_src;

  apr_hash_t *local_info;

  /* A hash containing the basenames of the nodes reported deleted by the
     repository (or NULL for no values). */
  apr_hash_t *deletes;

  /* Identifies those directory elements that get compared while running
     the crawler.  These elements should not be compared again when
     recursively looking for local modifications.

     This hash maps the basename of the node to an unimportant value.

     If the directory's properties have been compared, an item with hash
     key of "" will be present in the hash. */
  apr_hash_t *compared;

  /* The list of incoming BASE->repos propchanges. */
  apr_array_header_t *propchanges;

  /* Has a change on regular properties */
  svn_boolean_t has_propchange;

  /* The overall crawler editor baton. */
  struct edit_baton_t *eb;

  apr_pool_t *pool;
  int users;
};

/* File level baton.
 */
struct file_baton_t
{
  struct dir_baton_t *parent_baton;

  /* The name and path of this file as if they would be/are in the
     parent directory, diff session and local working copy. */
  const char *name;
  const char *relpath;
  const char *local_abspath;

  /* Processor state */
  void *pfb;
  svn_boolean_t skip;

  /* TRUE if the file is added by the editor drive. */
  svn_boolean_t added;
  /* TRUE if the node exists only on the repository side (op_depth 0 or added) */
  svn_boolean_t repos_only;
  /* TRUE if the node is to be compared with an unrelated node*/
  svn_boolean_t ignoring_ancestry;

  const svn_diff_source_t *left_src;
  const svn_diff_source_t *right_src;

  /* The list of incoming BASE->repos propchanges. */
  apr_array_header_t *propchanges;

  /* Has a change on regular properties */
  svn_boolean_t has_propchange;

  /* The current BASE checksum and props */
  const svn_checksum_t *base_checksum;
  apr_hash_t *base_props;

  /* The resulting from apply_textdelta */
  const char *temp_file_path;
  unsigned char result_digest[APR_MD5_DIGESTSIZE];

  /* The overall crawler editor baton. */
  struct edit_baton_t *eb;

  apr_pool_t *pool;
};

/* Create a new edit baton. TARGET_PATH/ANCHOR are working copy paths
 * that describe the root of the comparison. CALLBACKS/CALLBACK_BATON
 * define the callbacks to compare files. DEPTH defines if and how to
 * descend into subdirectories; see public doc string for exactly how.
 * IGNORE_ANCESTRY defines whether to utilize node ancestry when
 * calculating diffs.  USE_TEXT_BASE defines whether to compare
 * against working files or text-bases.  REVERSE_ORDER defines which
 * direction to perform the diff.
 *
 * CHANGELIST_FILTER is a list of const char * changelist names, used to
 * filter diff output responses to only those items in one of the
 * specified changelists, empty (or NULL altogether) if no changelist
 * filtering is requested.
 */
static svn_error_t *
make_edit_baton(struct edit_baton_t **edit_baton,
                svn_wc__db_t *db,
                const char *anchor_abspath,
                const char *target,
                const svn_wc_diff_callbacks4_t *callbacks,
                void *callback_baton,
                svn_depth_t depth,
                svn_boolean_t ignore_ancestry,
                svn_boolean_t show_copies_as_adds,
                svn_boolean_t use_git_diff_format,
                svn_boolean_t use_text_base,
                svn_boolean_t reverse_order,
                const apr_array_header_t *changelist_filter,
                svn_cancel_func_t cancel_func,
                void *cancel_baton,
                apr_pool_t *pool)
{
  apr_hash_t *changelist_hash = NULL;
  struct edit_baton_t *eb;
  const svn_diff_tree_processor_t *processor;

  SVN_ERR_ASSERT(svn_dirent_is_absolute(anchor_abspath));

  if (changelist_filter && changelist_filter->nelts)
    SVN_ERR(svn_hash_from_cstring_keys(&changelist_hash, changelist_filter,
                                       pool));

  SVN_ERR(svn_wc__wrap_diff_callbacks(&processor,
                                      callbacks, callback_baton, TRUE,
                                      pool, pool));

  if (reverse_order)
    processor = svn_diff__tree_processor_reverse_create(processor, NULL, pool);

  if (! show_copies_as_adds && !use_git_diff_format)
    processor = svn_diff__tree_processor_copy_as_changed_create(processor,
                                                                pool);

  eb = apr_pcalloc(pool, sizeof(*eb));
  eb->db = db;
  eb->anchor_abspath = apr_pstrdup(pool, anchor_abspath);
  eb->target = apr_pstrdup(pool, target);
  eb->processor = processor;
  eb->depth = depth;
  eb->ignore_ancestry = ignore_ancestry;
  eb->show_copies_as_adds = show_copies_as_adds;
  eb->local_before_remote = reverse_order;
  eb->diff_pristine = use_text_base;
  eb->changelist_hash = changelist_hash;
  eb->cancel_func = cancel_func;
  eb->cancel_baton = cancel_baton;
  eb->pool = pool;

  *edit_baton = eb;
  return SVN_NO_ERROR;
}

/* Create a new directory baton.  PATH is the directory path,
 * including anchor_path.  ADDED is set if this directory is being
 * added rather than replaced.  PARENT_BATON is the baton of the
 * parent directory, it will be null if this is the root of the
 * comparison hierarchy.  The directory and its parent may or may not
 * exist in the working copy.  EDIT_BATON is the overall crawler
 * editor baton.
 */
static struct dir_baton_t *
make_dir_baton(const char *path,
               struct dir_baton_t *parent_baton,
               struct edit_baton_t *eb,
               svn_boolean_t added,
               svn_depth_t depth,
               apr_pool_t *result_pool)
{
  apr_pool_t *dir_pool = svn_pool_create(parent_baton ? parent_baton->pool
                                                      : eb->pool);
  struct dir_baton_t *db = apr_pcalloc(dir_pool, sizeof(*db));

  db->parent_baton = parent_baton;

  /* Allocate 1 string for using as 3 strings */
  db->local_abspath = svn_dirent_join(eb->anchor_abspath, path, dir_pool);
  db->relpath = svn_dirent_skip_ancestor(eb->anchor_abspath, db->local_abspath);
  db->name = svn_dirent_basename(db->relpath, NULL);

  db->eb = eb;
  db->added = added;
  db->depth = depth;
  db->pool = dir_pool;
  db->propchanges = apr_array_make(dir_pool, 8, sizeof(svn_prop_t));
  db->compared = apr_hash_make(dir_pool);

  if (parent_baton != NULL)
    {
      parent_baton->users++;
    }

  db->users = 1;

  return db;
}

/* Create a new file baton.  PATH is the file path, including
 * anchor_path.  ADDED is set if this file is being added rather than
 * replaced.  PARENT_BATON is the baton of the parent directory.
 * The directory and its parent may or may not exist in the working copy.
 */
static struct file_baton_t *
make_file_baton(const char *path,
                svn_boolean_t added,
                struct dir_baton_t *parent_baton,
                apr_pool_t *result_pool)
{
  apr_pool_t *file_pool = svn_pool_create(result_pool);
  struct file_baton_t *fb = apr_pcalloc(file_pool, sizeof(*fb));
  struct edit_baton_t *eb = parent_baton->eb;

  fb->eb = eb;
  fb->parent_baton = parent_baton;
  fb->parent_baton->users++;

  /* Allocate 1 string for using as 3 strings */
  fb->local_abspath = svn_dirent_join(eb->anchor_abspath, path, file_pool);
  fb->relpath = svn_dirent_skip_ancestor(eb->anchor_abspath, fb->local_abspath);
  fb->name = svn_dirent_basename(fb->relpath, NULL);

  fb->added = added;
  fb->pool = file_pool;
  fb->propchanges  = apr_array_make(file_pool, 8, sizeof(svn_prop_t));

  return fb;
}

/* Destroy DB when there are no more registered users */
static svn_error_t *
maybe_done(struct dir_baton_t *db)
{
  db->users--;

  if (!db->users)
    {
      struct dir_baton_t *pb = db->parent_baton;

      svn_pool_clear(db->pool);

      if (pb != NULL)
        SVN_ERR(maybe_done(pb));
    }

  return SVN_NO_ERROR;
}

/* Standard check to see if a node is represented in the local working copy */
#define NOT_PRESENT(status)                                    \
            ((status) == svn_wc__db_status_not_present          \
             || (status) == svn_wc__db_status_excluded          \
             || (status) == svn_wc__db_status_server_excluded)

/* Prototypes of the local report tree walkers */
static svn_error_t *
report_local_only_file(struct edit_baton_t *eb,
                       const char *local_abspath,
                       const char *path,
                       void *parent_baton,
                       apr_pool_t *scratch_pool);
static svn_error_t *
report_local_only_dir(struct edit_baton_t *eb,
                      const char *local_abspath,
                      const char *path,
                      svn_depth_t depth,
                      void *parent_baton,
                      apr_pool_t *scratch_pool);
static svn_error_t *
report_base_only_file(struct edit_baton_t *eb,
                      const char *local_abspath,
                      const char *path,
                      void *parent_baton,
                      apr_pool_t *scratch_pool);
static svn_error_t *
report_base_only_dir(struct edit_baton_t *eb,
                     const char *local_abspath,
                     const char *path,
                     svn_depth_t depth,
                     void *parent_baton,
                     apr_pool_t *scratch_pool);


/* Diff the file PATH against the text base of its BASE layer.  At this
 * stage we are dealing with a file that does exist in the working copy.
 */
static svn_error_t *
base_working_diff(struct edit_baton_t *eb,
                  const char *local_abspath,
                  const char *relpath,
                  void *dir_baton,
                  apr_pool_t *scratch_pool)
{
  svn_wc__db_t *db = eb->db;
  void *file_baton = NULL;
  svn_boolean_t skip = FALSE;
  svn_wc__db_status_t status;
  svn_revnum_t revision;
  svn_boolean_t had_props;
  svn_boolean_t props_mod;
  svn_boolean_t files_same = FALSE;
  svn_wc__db_status_t base_status;
  const svn_checksum_t *working_checksum;
  const svn_checksum_t *checksum;
  svn_filesize_t recorded_size;
  apr_time_t recorded_time;
  const char *pristine_file;
  const char *local_file;
  svn_diff_source_t *left_src;
  svn_diff_source_t *right_src;
  apr_hash_t *base_props;
  apr_hash_t *local_props;
  apr_array_header_t *prop_changes;

  SVN_ERR_ASSERT(! eb->diff_pristine);

  /* If the item is not a member of a specified changelist (and there are
     some specified changelists), skip it. */
  if (! svn_wc__internal_changelist_match(db, local_abspath,
                                          eb->changelist_hash, scratch_pool))
    return SVN_NO_ERROR;

  SVN_ERR(svn_wc__db_read_info(&status, NULL, &revision, NULL, NULL, NULL,
                               NULL, NULL, NULL, NULL, &working_checksum, NULL,
                               NULL, NULL, NULL, NULL, NULL, &recorded_size,
                               &recorded_time, NULL, NULL, NULL, &had_props,
                               &props_mod, NULL, NULL, NULL,
                               db, local_abspath, scratch_pool, scratch_pool));
  checksum = working_checksum;

  assert(status == svn_wc__db_status_normal
         || status == svn_wc__db_status_added
         || (status == svn_wc__db_status_deleted && eb->diff_pristine));

  if (status != svn_wc__db_status_normal)
    {
      SVN_ERR(svn_wc__db_base_get_info(&base_status, NULL, &revision,
                                       NULL, NULL, NULL, NULL, NULL, NULL,
                                       NULL, &checksum, NULL, NULL, &had_props,
                                       NULL, NULL,
                                       db, local_abspath,
                                       scratch_pool, scratch_pool));
      recorded_size = SVN_INVALID_FILESIZE;
      recorded_time = 0;
      props_mod = TRUE; /* Requires compare */
    }
  else if (eb->diff_pristine)
    files_same = TRUE;
  else
    {
      const svn_io_dirent2_t *dirent;

      SVN_ERR(svn_io_stat_dirent2(&dirent, local_abspath,
                                  FALSE /* verify truename */,
                                  TRUE /* ingore_enoent */,
                                  scratch_pool, scratch_pool));

      if (dirent->kind == svn_node_file
          && dirent->filesize == recorded_size
          && dirent->mtime == recorded_time)
        {
          files_same = TRUE;
        }
    }

  if (files_same && !props_mod)
    return SVN_NO_ERROR; /* Cheap exit */

  assert(checksum);

  left_src = svn_diff__source_create(eb->revnum, scratch_pool);
  right_src = svn_diff__source_create(SVN_INVALID_REVNUM, scratch_pool);

  SVN_ERR(eb->processor->file_opened(&file_baton, &skip, relpath,
                                     left_src,
                                     right_src,
                                     NULL /* copyfrom_src */,
                                     dir_baton,
                                     eb->processor,
                                     scratch_pool, scratch_pool));

  if (skip)
    return SVN_NO_ERROR;

  SVN_ERR(svn_wc__db_pristine_get_path(&pristine_file,
                                       db, eb->anchor_abspath, checksum,
                                       scratch_pool, scratch_pool));

  if (eb->diff_pristine)
    SVN_ERR(svn_wc__db_pristine_get_path(&local_file,
                                         db, eb->anchor_abspath,
                                         working_checksum,
                                         scratch_pool, scratch_pool));
  else if (! (had_props || props_mod))
    local_file = local_abspath;
  else if (files_same)
    local_file = pristine_file;
  else
    SVN_ERR(svn_wc__internal_translated_file(
                            &local_file, local_abspath,
                            db, local_abspath,
                            SVN_WC_TRANSLATE_TO_NF
                                | SVN_WC_TRANSLATE_USE_GLOBAL_TMP,
                            eb->cancel_func, eb->cancel_baton,
                            scratch_pool, scratch_pool));

  if (! files_same)
    SVN_ERR(svn_io_files_contents_same_p(&files_same, local_file,
                                         pristine_file, scratch_pool));

  if (had_props)
    SVN_ERR(svn_wc__db_base_get_props(&base_props, db, local_abspath,
                                      scratch_pool, scratch_pool));
  else
    base_props = apr_hash_make(scratch_pool);

  if (status == svn_wc__db_status_normal && (eb->diff_pristine || !props_mod))
    local_props = base_props;
  else if (eb->diff_pristine)
    SVN_ERR(svn_wc__db_read_pristine_props(&local_props, db, local_abspath,
                                           scratch_pool, scratch_pool));
  else
    SVN_ERR(svn_wc__db_read_props(&local_props, db, local_abspath,
                                  scratch_pool, scratch_pool));

  SVN_ERR(svn_prop_diffs(&prop_changes, local_props, base_props, scratch_pool));

  if (prop_changes->nelts || !files_same)
    {
      SVN_ERR(eb->processor->file_changed(relpath,
                                          left_src,
                                          right_src,
                                          pristine_file,
                                          local_file,
                                          base_props,
                                          local_props,
                                          ! files_same,
                                          prop_changes,
                                          file_baton,
                                          eb->processor,
                                          scratch_pool));
    }
  else
    {
      SVN_ERR(eb->processor->file_closed(relpath,
                                         left_src,
                                         right_src,
                                         file_baton,
                                         eb->processor,
                                         scratch_pool));
    }

  return SVN_NO_ERROR;
}

static svn_error_t *
ensure_local_info(struct dir_baton_t *db,
                  apr_pool_t *scratch_pool)
{
  apr_hash_t *conflicts;

  if (db->local_info)
    return SVN_NO_ERROR;

  SVN_ERR(svn_wc__db_read_children_info(&db->local_info, &conflicts,
                                        db->eb->db, db->local_abspath,
                                        db->pool, scratch_pool));

  return SVN_NO_ERROR;
}

/* Called when the directory is closed to compare any elements that have
 * not yet been compared.  This identifies local, working copy only
 * changes.  At this stage we are dealing with files/directories that do
 * exist in the working copy.
 *
 * DIR_BATON is the baton for the directory.
 */
static svn_error_t *
walk_local_nodes_diff(struct edit_baton_t *eb,
                      const char *local_abspath,
                      const char *path,
                      svn_depth_t depth,
                      apr_hash_t *compared,
                      void *parent_baton,
                      apr_pool_t *scratch_pool)
{
  svn_wc__db_t *db = eb->db;
  svn_boolean_t in_anchor_not_target;
  apr_pool_t *iterpool;
  void *dir_baton = NULL;
  svn_boolean_t skip = FALSE;
  svn_boolean_t skip_children = FALSE;
  svn_revnum_t revision;
  svn_boolean_t props_mod;
  svn_diff_source_t *left_src;
  svn_diff_source_t *right_src;

  /* Everything we do below is useless if we are comparing to BASE. */
  if (eb->diff_pristine)
    return SVN_NO_ERROR;

  /* Determine if this is the anchor directory if the anchor is different
     to the target. When the target is a file, the anchor is the parent
     directory and if this is that directory the non-target entries must be
     skipped. */
  in_anchor_not_target = ((*path == '\0') && (*eb->target != '\0'));

  iterpool = svn_pool_create(scratch_pool);

  SVN_ERR(svn_wc__db_read_info(NULL, NULL, &revision, NULL, NULL, NULL, NULL,
                               NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
                               NULL, NULL, NULL, NULL, NULL, NULL, NULL,
                               NULL, &props_mod, NULL, NULL, NULL,
                               db, local_abspath, scratch_pool, scratch_pool));

  left_src = svn_diff__source_create(revision, scratch_pool);
  right_src = svn_diff__source_create(SVN_INVALID_REVNUM, scratch_pool);

  if (compared)
    {
      dir_baton = parent_baton;
      skip = TRUE;
    }
  else if (!in_anchor_not_target)
    SVN_ERR(eb->processor->dir_opened(&dir_baton, &skip, &skip_children,
                                      path,
                                      left_src,
                                      right_src,
                                      NULL /* copyfrom_src */,
                                      parent_baton,
                                      eb->processor,
                                      scratch_pool, scratch_pool));


  if (!skip_children && depth != svn_depth_empty)
    {
      apr_hash_t *nodes;
      apr_hash_t *conflicts;
      apr_hash_index_t *hi;
      svn_depth_t depth_below_here = depth;
      svn_boolean_t diff_files;
      svn_boolean_t diff_dirs;

      if (depth_below_here == svn_depth_immediates)
        depth_below_here = svn_depth_empty;

      diff_files = (depth == svn_depth_unknown
                   || depth >= svn_depth_files);
      diff_dirs = (depth == svn_depth_unknown
                   || depth >= svn_depth_immediates);

      SVN_ERR(svn_wc__db_read_children_info(&nodes, &conflicts,
                                            db, local_abspath,
                                            scratch_pool, iterpool));

      for (hi = apr_hash_first(scratch_pool, nodes);
           hi; hi = apr_hash_next(hi))
        {
          const char *name = svn__apr_hash_index_key(hi);
          struct svn_wc__db_info_t *info = svn__apr_hash_index_val(hi);
          const char *child_abspath;
          const char *child_relpath;
          svn_boolean_t repos_only;
          svn_boolean_t do_diff;
          svn_boolean_t local_only;
          svn_revnum_t revision = SVN_INVALID_REVNUM;
          svn_kind_t base_kind;

          if (eb->cancel_func)
            SVN_ERR(eb->cancel_func(eb->cancel_baton));

          /* In the anchor directory, if the anchor is not the target then all
             entries other than the target should not be diff'd. Running diff
             on one file in a directory should not diff other files in that
             directory. */
          if (in_anchor_not_target && strcmp(eb->target, name))
            continue;

          if (compared && svn_hash_gets(compared, name))
            continue;

          if (NOT_PRESENT(info->status))
            continue;

          assert(info->status == svn_wc__db_status_normal
                 || info->status == svn_wc__db_status_added
                 || info->status == svn_wc__db_status_deleted);

          svn_pool_clear(iterpool);
          child_abspath = svn_dirent_join(local_abspath, name, iterpool);
          child_relpath = svn_relpath_join(path, name, iterpool);

          repos_only = FALSE;
          local_only = FALSE;
          do_diff = FALSE;

          if (!info->have_base)
            {
              local_only = TRUE; /* Only report additions */
            }
          else if (info->status == svn_wc__db_status_normal)
            {
              /* Simple diff */
              base_kind = info->kind;
              revision = info->revnum;
              do_diff = TRUE;
            }
          else if (info->status == svn_wc__db_status_deleted
                   && (!eb->diff_pristine || !info->have_more_work))
            {
              svn_wc__db_status_t base_status;
              repos_only = TRUE;
              SVN_ERR(svn_wc__db_base_get_info(&base_status, &base_kind, NULL,
                                               NULL, NULL, NULL, NULL, NULL,
                                               NULL, NULL, NULL, NULL, NULL,
                                               NULL, NULL, NULL,
                                               db, child_abspath,
                                               iterpool, iterpool));

              if (NOT_PRESENT(base_status))
                continue;<commit_msg>* subversion/libsvn_wc/diff_editor.c   (walk_local_nodes_diff): Remove unused variable.<commit_after>/*
 * diff_editor.c -- The diff editor for comparing the working copy against the
 *                  repository.
 *
 * ====================================================================
 *    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.
 * ====================================================================
 */

/*
 * This code uses an svn_delta_editor_t editor driven by
 * svn_wc_crawl_revisions (like the update command) to retrieve the
 * differences between the working copy and the requested repository
 * version. Rather than updating the working copy, this new editor creates
 * temporary files that contain the pristine repository versions. When the
 * crawler closes the files the editor calls back to a client layer
 * function to compare the working copy and the temporary file. There is
 * only ever one temporary file in existence at any time.
 *
 * When the crawler closes a directory, the editor then calls back to the
 * client layer to compare any remaining files that may have been modified
 * locally. Added directories do not have corresponding temporary
 * directories created, as they are not needed.
 *
 * The diff result from this editor is a combination of the restructuring
 * operations from the repository with the local restructurings since checking
 * out.
 *
 * ### TODO: Make sure that we properly support and report multi layered
 *           operations instead of only simple file replacements.
 *
 * ### TODO: Replacements where the node kind changes needs support. It
 * mostly works when the change is in the repository, but not when it is
 * in the working copy.
 *
 * ### TODO: Do we need to support copyfrom?
 *
 */

#include <apr_hash.h>
#include <apr_md5.h>

#include <assert.h>

#include "svn_error.h"
#include "svn_pools.h"
#include "svn_dirent_uri.h"
#include "svn_path.h"
#include "svn_hash.h"

#include "private/svn_subr_private.h"
#include "private/svn_wc_private.h"
#include "private/svn_diff_tree.h"
#include "private/svn_editor.h"

#include "wc.h"
#include "props.h"
#include "adm_files.h"
#include "translate.h"

#include "svn_private_config.h"

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



/* Overall crawler editor baton.
 */
struct edit_baton_t 
{
  /* A wc db. */
  svn_wc__db_t *db;

  /* A diff tree processor, receiving the result of the diff. */
  const svn_diff_tree_processor_t *processor;

  /* A boolean indicating whether local additions should be reported before
     remote deletes. The processor can transform adds in deletes and deletes
     in adds, but it can't reorder the output. */
  svn_boolean_t local_before_remote;

  /* ANCHOR/TARGET represent the base of the hierarchy to be compared. */
  const char *target;
  const char *anchor_abspath;

  /* Target revision */
  svn_revnum_t revnum;

  /* Was the root opened? */
  svn_boolean_t root_opened;

  /* How does this diff descend as seen from target? */
  svn_depth_t depth;

  /* Should this diff ignore node ancestry? */
  svn_boolean_t ignore_ancestry;

  /* Should this diff not compare copied files with their source? */
  svn_boolean_t show_copies_as_adds;

  /* Possibly diff repos against text-bases instead of working files. */
  svn_boolean_t diff_pristine;

  /* Hash whose keys are const char * changelist names. */
  apr_hash_t *changelist_hash;

  /* Cancel function/baton */
  svn_cancel_func_t cancel_func;
  void *cancel_baton;

  apr_pool_t *pool;
};

/* Directory level baton.
 */
struct dir_baton_t
{
  /* Reference to parent directory baton (or NULL for the root) */
  struct dir_baton_t *parent_baton;

  /* The depth at which this directory should be diffed. */
  svn_depth_t depth;

  /* The name and path of this directory as if they would be/are in the
      local working copy. */
  const char *name;
  const char *relpath;
  const char *local_abspath;

  /* TRUE if the file is added by the editor drive. */
  svn_boolean_t added;
  /* TRUE if the node exists only on the repository side (op_depth 0 or added) */
  svn_boolean_t repos_only;
  /* TRUE if the node is to be compared with an unrelated node*/
  svn_boolean_t ignoring_ancestry;

  /* Processor state */
  void *pdb;
  svn_boolean_t skip;
  svn_boolean_t skip_children;

  svn_diff_source_t *left_src;
  svn_diff_source_t *right_src;

  apr_hash_t *local_info;

  /* A hash containing the basenames of the nodes reported deleted by the
     repository (or NULL for no values). */
  apr_hash_t *deletes;

  /* Identifies those directory elements that get compared while running
     the crawler.  These elements should not be compared again when
     recursively looking for local modifications.

     This hash maps the basename of the node to an unimportant value.

     If the directory's properties have been compared, an item with hash
     key of "" will be present in the hash. */
  apr_hash_t *compared;

  /* The list of incoming BASE->repos propchanges. */
  apr_array_header_t *propchanges;

  /* Has a change on regular properties */
  svn_boolean_t has_propchange;

  /* The overall crawler editor baton. */
  struct edit_baton_t *eb;

  apr_pool_t *pool;
  int users;
};

/* File level baton.
 */
struct file_baton_t
{
  struct dir_baton_t *parent_baton;

  /* The name and path of this file as if they would be/are in the
     parent directory, diff session and local working copy. */
  const char *name;
  const char *relpath;
  const char *local_abspath;

  /* Processor state */
  void *pfb;
  svn_boolean_t skip;

  /* TRUE if the file is added by the editor drive. */
  svn_boolean_t added;
  /* TRUE if the node exists only on the repository side (op_depth 0 or added) */
  svn_boolean_t repos_only;
  /* TRUE if the node is to be compared with an unrelated node*/
  svn_boolean_t ignoring_ancestry;

  const svn_diff_source_t *left_src;
  const svn_diff_source_t *right_src;

  /* The list of incoming BASE->repos propchanges. */
  apr_array_header_t *propchanges;

  /* Has a change on regular properties */
  svn_boolean_t has_propchange;

  /* The current BASE checksum and props */
  const svn_checksum_t *base_checksum;
  apr_hash_t *base_props;

  /* The resulting from apply_textdelta */
  const char *temp_file_path;
  unsigned char result_digest[APR_MD5_DIGESTSIZE];

  /* The overall crawler editor baton. */
  struct edit_baton_t *eb;

  apr_pool_t *pool;
};

/* Create a new edit baton. TARGET_PATH/ANCHOR are working copy paths
 * that describe the root of the comparison. CALLBACKS/CALLBACK_BATON
 * define the callbacks to compare files. DEPTH defines if and how to
 * descend into subdirectories; see public doc string for exactly how.
 * IGNORE_ANCESTRY defines whether to utilize node ancestry when
 * calculating diffs.  USE_TEXT_BASE defines whether to compare
 * against working files or text-bases.  REVERSE_ORDER defines which
 * direction to perform the diff.
 *
 * CHANGELIST_FILTER is a list of const char * changelist names, used to
 * filter diff output responses to only those items in one of the
 * specified changelists, empty (or NULL altogether) if no changelist
 * filtering is requested.
 */
static svn_error_t *
make_edit_baton(struct edit_baton_t **edit_baton,
                svn_wc__db_t *db,
                const char *anchor_abspath,
                const char *target,
                const svn_wc_diff_callbacks4_t *callbacks,
                void *callback_baton,
                svn_depth_t depth,
                svn_boolean_t ignore_ancestry,
                svn_boolean_t show_copies_as_adds,
                svn_boolean_t use_git_diff_format,
                svn_boolean_t use_text_base,
                svn_boolean_t reverse_order,
                const apr_array_header_t *changelist_filter,
                svn_cancel_func_t cancel_func,
                void *cancel_baton,
                apr_pool_t *pool)
{
  apr_hash_t *changelist_hash = NULL;
  struct edit_baton_t *eb;
  const svn_diff_tree_processor_t *processor;

  SVN_ERR_ASSERT(svn_dirent_is_absolute(anchor_abspath));

  if (changelist_filter && changelist_filter->nelts)
    SVN_ERR(svn_hash_from_cstring_keys(&changelist_hash, changelist_filter,
                                       pool));

  SVN_ERR(svn_wc__wrap_diff_callbacks(&processor,
                                      callbacks, callback_baton, TRUE,
                                      pool, pool));

  if (reverse_order)
    processor = svn_diff__tree_processor_reverse_create(processor, NULL, pool);

  if (! show_copies_as_adds && !use_git_diff_format)
    processor = svn_diff__tree_processor_copy_as_changed_create(processor,
                                                                pool);

  eb = apr_pcalloc(pool, sizeof(*eb));
  eb->db = db;
  eb->anchor_abspath = apr_pstrdup(pool, anchor_abspath);
  eb->target = apr_pstrdup(pool, target);
  eb->processor = processor;
  eb->depth = depth;
  eb->ignore_ancestry = ignore_ancestry;
  eb->show_copies_as_adds = show_copies_as_adds;
  eb->local_before_remote = reverse_order;
  eb->diff_pristine = use_text_base;
  eb->changelist_hash = changelist_hash;
  eb->cancel_func = cancel_func;
  eb->cancel_baton = cancel_baton;
  eb->pool = pool;

  *edit_baton = eb;
  return SVN_NO_ERROR;
}

/* Create a new directory baton.  PATH is the directory path,
 * including anchor_path.  ADDED is set if this directory is being
 * added rather than replaced.  PARENT_BATON is the baton of the
 * parent directory, it will be null if this is the root of the
 * comparison hierarchy.  The directory and its parent may or may not
 * exist in the working copy.  EDIT_BATON is the overall crawler
 * editor baton.
 */
static struct dir_baton_t *
make_dir_baton(const char *path,
               struct dir_baton_t *parent_baton,
               struct edit_baton_t *eb,
               svn_boolean_t added,
               svn_depth_t depth,
               apr_pool_t *result_pool)
{
  apr_pool_t *dir_pool = svn_pool_create(parent_baton ? parent_baton->pool
                                                      : eb->pool);
  struct dir_baton_t *db = apr_pcalloc(dir_pool, sizeof(*db));

  db->parent_baton = parent_baton;

  /* Allocate 1 string for using as 3 strings */
  db->local_abspath = svn_dirent_join(eb->anchor_abspath, path, dir_pool);
  db->relpath = svn_dirent_skip_ancestor(eb->anchor_abspath, db->local_abspath);
  db->name = svn_dirent_basename(db->relpath, NULL);

  db->eb = eb;
  db->added = added;
  db->depth = depth;
  db->pool = dir_pool;
  db->propchanges = apr_array_make(dir_pool, 8, sizeof(svn_prop_t));
  db->compared = apr_hash_make(dir_pool);

  if (parent_baton != NULL)
    {
      parent_baton->users++;
    }

  db->users = 1;

  return db;
}

/* Create a new file baton.  PATH is the file path, including
 * anchor_path.  ADDED is set if this file is being added rather than
 * replaced.  PARENT_BATON is the baton of the parent directory.
 * The directory and its parent may or may not exist in the working copy.
 */
static struct file_baton_t *
make_file_baton(const char *path,
                svn_boolean_t added,
                struct dir_baton_t *parent_baton,
                apr_pool_t *result_pool)
{
  apr_pool_t *file_pool = svn_pool_create(result_pool);
  struct file_baton_t *fb = apr_pcalloc(file_pool, sizeof(*fb));
  struct edit_baton_t *eb = parent_baton->eb;

  fb->eb = eb;
  fb->parent_baton = parent_baton;
  fb->parent_baton->users++;

  /* Allocate 1 string for using as 3 strings */
  fb->local_abspath = svn_dirent_join(eb->anchor_abspath, path, file_pool);
  fb->relpath = svn_dirent_skip_ancestor(eb->anchor_abspath, fb->local_abspath);
  fb->name = svn_dirent_basename(fb->relpath, NULL);

  fb->added = added;
  fb->pool = file_pool;
  fb->propchanges  = apr_array_make(file_pool, 8, sizeof(svn_prop_t));

  return fb;
}

/* Destroy DB when there are no more registered users */
static svn_error_t *
maybe_done(struct dir_baton_t *db)
{
  db->users--;

  if (!db->users)
    {
      struct dir_baton_t *pb = db->parent_baton;

      svn_pool_clear(db->pool);

      if (pb != NULL)
        SVN_ERR(maybe_done(pb));
    }

  return SVN_NO_ERROR;
}

/* Standard check to see if a node is represented in the local working copy */
#define NOT_PRESENT(status)                                    \
            ((status) == svn_wc__db_status_not_present          \
             || (status) == svn_wc__db_status_excluded          \
             || (status) == svn_wc__db_status_server_excluded)

/* Prototypes of the local report tree walkers */
static svn_error_t *
report_local_only_file(struct edit_baton_t *eb,
                       const char *local_abspath,
                       const char *path,
                       void *parent_baton,
                       apr_pool_t *scratch_pool);
static svn_error_t *
report_local_only_dir(struct edit_baton_t *eb,
                      const char *local_abspath,
                      const char *path,
                      svn_depth_t depth,
                      void *parent_baton,
                      apr_pool_t *scratch_pool);
static svn_error_t *
report_base_only_file(struct edit_baton_t *eb,
                      const char *local_abspath,
                      const char *path,
                      void *parent_baton,
                      apr_pool_t *scratch_pool);
static svn_error_t *
report_base_only_dir(struct edit_baton_t *eb,
                     const char *local_abspath,
                     const char *path,
                     svn_depth_t depth,
                     void *parent_baton,
                     apr_pool_t *scratch_pool);


/* Diff the file PATH against the text base of its BASE layer.  At this
 * stage we are dealing with a file that does exist in the working copy.
 */
static svn_error_t *
base_working_diff(struct edit_baton_t *eb,
                  const char *local_abspath,
                  const char *relpath,
                  void *dir_baton,
                  apr_pool_t *scratch_pool)
{
  svn_wc__db_t *db = eb->db;
  void *file_baton = NULL;
  svn_boolean_t skip = FALSE;
  svn_wc__db_status_t status;
  svn_revnum_t revision;
  svn_boolean_t had_props;
  svn_boolean_t props_mod;
  svn_boolean_t files_same = FALSE;
  svn_wc__db_status_t base_status;
  const svn_checksum_t *working_checksum;
  const svn_checksum_t *checksum;
  svn_filesize_t recorded_size;
  apr_time_t recorded_time;
  const char *pristine_file;
  const char *local_file;
  svn_diff_source_t *left_src;
  svn_diff_source_t *right_src;
  apr_hash_t *base_props;
  apr_hash_t *local_props;
  apr_array_header_t *prop_changes;

  SVN_ERR_ASSERT(! eb->diff_pristine);

  /* If the item is not a member of a specified changelist (and there are
     some specified changelists), skip it. */
  if (! svn_wc__internal_changelist_match(db, local_abspath,
                                          eb->changelist_hash, scratch_pool))
    return SVN_NO_ERROR;

  SVN_ERR(svn_wc__db_read_info(&status, NULL, &revision, NULL, NULL, NULL,
                               NULL, NULL, NULL, NULL, &working_checksum, NULL,
                               NULL, NULL, NULL, NULL, NULL, &recorded_size,
                               &recorded_time, NULL, NULL, NULL, &had_props,
                               &props_mod, NULL, NULL, NULL,
                               db, local_abspath, scratch_pool, scratch_pool));
  checksum = working_checksum;

  assert(status == svn_wc__db_status_normal
         || status == svn_wc__db_status_added
         || (status == svn_wc__db_status_deleted && eb->diff_pristine));

  if (status != svn_wc__db_status_normal)
    {
      SVN_ERR(svn_wc__db_base_get_info(&base_status, NULL, &revision,
                                       NULL, NULL, NULL, NULL, NULL, NULL,
                                       NULL, &checksum, NULL, NULL, &had_props,
                                       NULL, NULL,
                                       db, local_abspath,
                                       scratch_pool, scratch_pool));
      recorded_size = SVN_INVALID_FILESIZE;
      recorded_time = 0;
      props_mod = TRUE; /* Requires compare */
    }
  else if (eb->diff_pristine)
    files_same = TRUE;
  else
    {
      const svn_io_dirent2_t *dirent;

      SVN_ERR(svn_io_stat_dirent2(&dirent, local_abspath,
                                  FALSE /* verify truename */,
                                  TRUE /* ingore_enoent */,
                                  scratch_pool, scratch_pool));

      if (dirent->kind == svn_node_file
          && dirent->filesize == recorded_size
          && dirent->mtime == recorded_time)
        {
          files_same = TRUE;
        }
    }

  if (files_same && !props_mod)
    return SVN_NO_ERROR; /* Cheap exit */

  assert(checksum);

  left_src = svn_diff__source_create(eb->revnum, scratch_pool);
  right_src = svn_diff__source_create(SVN_INVALID_REVNUM, scratch_pool);

  SVN_ERR(eb->processor->file_opened(&file_baton, &skip, relpath,
                                     left_src,
                                     right_src,
                                     NULL /* copyfrom_src */,
                                     dir_baton,
                                     eb->processor,
                                     scratch_pool, scratch_pool));

  if (skip)
    return SVN_NO_ERROR;

  SVN_ERR(svn_wc__db_pristine_get_path(&pristine_file,
                                       db, eb->anchor_abspath, checksum,
                                       scratch_pool, scratch_pool));

  if (eb->diff_pristine)
    SVN_ERR(svn_wc__db_pristine_get_path(&local_file,
                                         db, eb->anchor_abspath,
                                         working_checksum,
                                         scratch_pool, scratch_pool));
  else if (! (had_props || props_mod))
    local_file = local_abspath;
  else if (files_same)
    local_file = pristine_file;
  else
    SVN_ERR(svn_wc__internal_translated_file(
                            &local_file, local_abspath,
                            db, local_abspath,
                            SVN_WC_TRANSLATE_TO_NF
                                | SVN_WC_TRANSLATE_USE_GLOBAL_TMP,
                            eb->cancel_func, eb->cancel_baton,
                            scratch_pool, scratch_pool));

  if (! files_same)
    SVN_ERR(svn_io_files_contents_same_p(&files_same, local_file,
                                         pristine_file, scratch_pool));

  if (had_props)
    SVN_ERR(svn_wc__db_base_get_props(&base_props, db, local_abspath,
                                      scratch_pool, scratch_pool));
  else
    base_props = apr_hash_make(scratch_pool);

  if (status == svn_wc__db_status_normal && (eb->diff_pristine || !props_mod))
    local_props = base_props;
  else if (eb->diff_pristine)
    SVN_ERR(svn_wc__db_read_pristine_props(&local_props, db, local_abspath,
                                           scratch_pool, scratch_pool));
  else
    SVN_ERR(svn_wc__db_read_props(&local_props, db, local_abspath,
                                  scratch_pool, scratch_pool));

  SVN_ERR(svn_prop_diffs(&prop_changes, local_props, base_props, scratch_pool));

  if (prop_changes->nelts || !files_same)
    {
      SVN_ERR(eb->processor->file_changed(relpath,
                                          left_src,
                                          right_src,
                                          pristine_file,
                                          local_file,
                                          base_props,
                                          local_props,
                                          ! files_same,
                                          prop_changes,
                                          file_baton,
                                          eb->processor,
                                          scratch_pool));
    }
  else
    {
      SVN_ERR(eb->processor->file_closed(relpath,
                                         left_src,
                                         right_src,
                                         file_baton,
                                         eb->processor,
                                         scratch_pool));
    }

  return SVN_NO_ERROR;
}

static svn_error_t *
ensure_local_info(struct dir_baton_t *db,
                  apr_pool_t *scratch_pool)
{
  apr_hash_t *conflicts;

  if (db->local_info)
    return SVN_NO_ERROR;

  SVN_ERR(svn_wc__db_read_children_info(&db->local_info, &conflicts,
                                        db->eb->db, db->local_abspath,
                                        db->pool, scratch_pool));

  return SVN_NO_ERROR;
}

/* Called when the directory is closed to compare any elements that have
 * not yet been compared.  This identifies local, working copy only
 * changes.  At this stage we are dealing with files/directories that do
 * exist in the working copy.
 *
 * DIR_BATON is the baton for the directory.
 */
static svn_error_t *
walk_local_nodes_diff(struct edit_baton_t *eb,
                      const char *local_abspath,
                      const char *path,
                      svn_depth_t depth,
                      apr_hash_t *compared,
                      void *parent_baton,
                      apr_pool_t *scratch_pool)
{
  svn_wc__db_t *db = eb->db;
  svn_boolean_t in_anchor_not_target;
  apr_pool_t *iterpool;
  void *dir_baton = NULL;
  svn_boolean_t skip = FALSE;
  svn_boolean_t skip_children = FALSE;
  svn_revnum_t revision;
  svn_boolean_t props_mod;
  svn_diff_source_t *left_src;
  svn_diff_source_t *right_src;

  /* Everything we do below is useless if we are comparing to BASE. */
  if (eb->diff_pristine)
    return SVN_NO_ERROR;

  /* Determine if this is the anchor directory if the anchor is different
     to the target. When the target is a file, the anchor is the parent
     directory and if this is that directory the non-target entries must be
     skipped. */
  in_anchor_not_target = ((*path == '\0') && (*eb->target != '\0'));

  iterpool = svn_pool_create(scratch_pool);

  SVN_ERR(svn_wc__db_read_info(NULL, NULL, &revision, NULL, NULL, NULL, NULL,
                               NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
                               NULL, NULL, NULL, NULL, NULL, NULL, NULL,
                               NULL, &props_mod, NULL, NULL, NULL,
                               db, local_abspath, scratch_pool, scratch_pool));

  left_src = svn_diff__source_create(revision, scratch_pool);
  right_src = svn_diff__source_create(SVN_INVALID_REVNUM, scratch_pool);

  if (compared)
    {
      dir_baton = parent_baton;
      skip = TRUE;
    }
  else if (!in_anchor_not_target)
    SVN_ERR(eb->processor->dir_opened(&dir_baton, &skip, &skip_children,
                                      path,
                                      left_src,
                                      right_src,
                                      NULL /* copyfrom_src */,
                                      parent_baton,
                                      eb->processor,
                                      scratch_pool, scratch_pool));


  if (!skip_children && depth != svn_depth_empty)
    {
      apr_hash_t *nodes;
      apr_hash_t *conflicts;
      apr_hash_index_t *hi;
      svn_depth_t depth_below_here = depth;
      svn_boolean_t diff_files;
      svn_boolean_t diff_dirs;

      if (depth_below_here == svn_depth_immediates)
        depth_below_here = svn_depth_empty;

      diff_files = (depth == svn_depth_unknown
                   || depth >= svn_depth_files);
      diff_dirs = (depth == svn_depth_unknown
                   || depth >= svn_depth_immediates);

      SVN_ERR(svn_wc__db_read_children_info(&nodes, &conflicts,
                                            db, local_abspath,
                                            scratch_pool, iterpool));

      for (hi = apr_hash_first(scratch_pool, nodes);
           hi; hi = apr_hash_next(hi))
        {
          const char *name = svn__apr_hash_index_key(hi);
          struct svn_wc__db_info_t *info = svn__apr_hash_index_val(hi);
          const char *child_abspath;
          const char *child_relpath;
          svn_boolean_t repos_only;
          svn_boolean_t do_diff;
          svn_boolean_t local_only;
          svn_kind_t base_kind;

          if (eb->cancel_func)
            SVN_ERR(eb->cancel_func(eb->cancel_baton));

          /* In the anchor directory, if the anchor is not the target then all
             entries other than the target should not be diff'd. Running diff
             on one file in a directory should not diff other files in that
             directory. */
          if (in_anchor_not_target && strcmp(eb->target, name))
            continue;

          if (compared && svn_hash_gets(compared, name))
            continue;

          if (NOT_PRESENT(info->status))
            continue;

          assert(info->status == svn_wc__db_status_normal
                 || info->status == svn_wc__db_status_added
                 || info->status == svn_wc__db_status_deleted);

          svn_pool_clear(iterpool);
          child_abspath = svn_dirent_join(local_abspath, name, iterpool);
          child_relpath = svn_relpath_join(path, name, iterpool);

          repos_only = FALSE;
          local_only = FALSE;
          do_diff = FALSE;

          if (!info->have_base)
            {
              local_only = TRUE; /* Only report additions */
            }
          else if (info->status == svn_wc__db_status_normal)
            {
              /* Simple diff */
              base_kind = info->kind;
              do_diff = TRUE;
            }
          else if (info->status == svn_wc__db_status_deleted
                   && (!eb->diff_pristine || !info->have_more_work))
            {
              svn_wc__db_status_t base_status;
              repos_only = TRUE;
              SVN_ERR(svn_wc__db_base_get_info(&base_status, &base_kind, NULL,
                                               NULL, NULL, NULL, NULL, NULL,
                                               NULL, NULL, NULL, NULL, NULL,
                                               NULL, NULL, NULL,
                                               db, child_abspath,
                                               iterpool, iterpool));

              if (NOT_PRESENT(base_status))
                continue;
{'fraction_non_alphanumeric': 0.08201430950246116, 'fraction_numerical': 0.0007685410529012425, 'mean_word_length': 2.086175739778631, 'pattern_counts': {'":': 0, '<': 9, '<?xml version=': 0, '>': 224, 'https://': 0, 'lorem ipsum': 0, 'www.': 2, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 200, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '1766953', 'n_tokens_mistral': 16649, 'n_tokens_neox': 16144, 'n_words': 4395}