/*
 * entries.c :  manipulating the administrative `entries' file.
 *
 * ====================================================================
 *    Licensed to the Apache Software Foundation (ASF) under one
 *    or more contributor license agreements.  See the NOTICE file
 *    distributed with this work for additional information
 *    regarding copyright ownership.  The ASF licenses this file
 *    to you under the Apache License, Version 2.0 (the
 *    "License"); you may not use this file except in compliance
 *    with the License.  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 *    Unless required by applicable law or agreed to in writing,
 *    software distributed under the License is distributed on an
 *    "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 *    KIND, either express or implied.  See the License for the
 *    specific language governing permissions and limitations
 *    under the License.
 * ====================================================================
 */

#include <string.h>
#include <assert.h>

#include <apr_strings.h>

#include "svn_error.h"
#include "svn_types.h"
#include "svn_time.h"
#include "svn_pools.h"
#include "svn_dirent_uri.h"
#include "svn_path.h"
#include "svn_ctype.h"
#include "svn_string.h"
#include "svn_hash.h"

#include "wc.h"
#include "adm_files.h"
#include "conflicts.h"
#include "entries.h"
#include "lock.h"
#include "tree_conflicts.h"
#include "wc_db.h"
#include "wc-queries.h"  /* for STMT_*  */

#include "svn_private_config.h"
#include "private/svn_wc_private.h"
#include "private/svn_sqlite.h"

#define MAYBE_ALLOC(x,p) ((x) ? (x) : apr_pcalloc((p), sizeof(*(x))))


/* Temporary structures which mirror the tables in wc-metadata.sql.
   For detailed descriptions of each field, see that file. */
typedef struct db_node_t {
  apr_int64_t wc_id;
  const char *local_relpath;
  int op_depth;
  apr_int64_t repos_id;
  const char *repos_relpath;
  const char *parent_relpath;
  svn_wc__db_status_t presence;
  svn_revnum_t revision;
  svn_node_kind_t kind;
  svn_checksum_t *checksum;
  svn_filesize_t recorded_size;
  svn_revnum_t changed_rev;
  apr_time_t changed_date;
  const char *changed_author;
  svn_depth_t depth;
  apr_time_t recorded_time;
  apr_hash_t *properties;
  svn_boolean_t file_external;
  apr_array_header_t *inherited_props;
} db_node_t;

typedef struct db_actual_node_t {
  apr_int64_t wc_id;
  const char *local_relpath;
  const char *parent_relpath;
  apr_hash_t *properties;
  const char *conflict_old;
  const char *conflict_new;
  const char *conflict_working;
  const char *prop_reject;
  const char *changelist;
  /* ### enum for text_mod */
  const char *tree_conflict_data;
} db_actual_node_t;



/*** reading and writing the entries file ***/


/* */
static svn_wc_entry_t *
alloc_entry(apr_pool_t *pool)
{
  svn_wc_entry_t *entry = apr_pcalloc(pool, sizeof(*entry));
  entry->revision = SVN_INVALID_REVNUM;
  entry->copyfrom_rev = SVN_INVALID_REVNUM;
  entry->cmt_rev = SVN_INVALID_REVNUM;
  entry->kind = svn_node_none;
  entry->working_size = SVN_WC_ENTRY_WORKING_SIZE_UNKNOWN;
  entry->depth = svn_depth_infinity;
  entry->file_external_peg_rev.kind = svn_opt_revision_unspecified;
  entry->file_external_rev.kind = svn_opt_revision_unspecified;
  return entry;
}


/* Is the entry in a 'hidden' state in the sense of the 'show_hidden'
 * switches on svn_wc_entries_read(), svn_wc_walk_entries*(), etc.? */
svn_error_t *
svn_wc__entry_is_hidden(svn_boolean_t *hidden, const svn_wc_entry_t *entry)
{
  /* In English, the condition is: "the entry is not present, and I haven't
     scheduled something over the top of it."  */
  if (entry->deleted
      || entry->absent
      || entry->depth == svn_depth_exclude)
    {
      /* These kinds of nodes cannot be marked for deletion (which also
         means no "replace" either).  */
      SVN_ERR_ASSERT(entry->schedule == svn_wc_schedule_add
                     || entry->schedule == svn_wc_schedule_normal);

      /* Hidden if something hasn't been added over it.

         ### is this even possible with absent or excluded nodes?  */
      *hidden = entry->schedule != svn_wc_schedule_add;
    }
  else
    *hidden = FALSE;

  return SVN_NO_ERROR;
}


/* Hit the database to check the file external information for the given
   entry.  The entry will be modified in place. */
static svn_error_t *
check_file_external(svn_wc_entry_t *entry,
                    svn_wc__db_t *db,
                    const char *local_abspath,
                    const char *wri_abspath,
                    apr_pool_t *result_pool,
                    apr_pool_t *scratch_pool)
{
  svn_wc__db_status_t status;
  svn_node_kind_t kind;
  const char *repos_relpath;
  svn_revnum_t peg_revision;
  svn_revnum_t revision;
  svn_error_t *err;

  err = svn_wc__db_external_read(&status, &kind, NULL, NULL, NULL,
                                 &repos_relpath, &peg_revision, &revision,
                                 db, local_abspath, wri_abspath,
                                 result_pool, scratch_pool);

  if (err)
    {
      if (err->apr_err != SVN_ERR_WC_PATH_NOT_FOUND)
        return svn_error_trace(err);

      svn_error_clear(err);
      return SVN_NO_ERROR;
    }

  if (status == svn_wc__db_status_normal
      && kind == svn_node_file)
    {
      entry->file_external_path = repos_relpath;
      if (SVN_IS_VALID_REVNUM(peg_revision))
        {
          entry->file_external_peg_rev.kind = svn_opt_revision_number;
          entry->file_external_peg_rev.value.number = peg_revision;
          entry->file_external_rev = entry->file_external_peg_rev;
        }
      if (SVN_IS_VALID_REVNUM(revision))
        {
          entry->file_external_rev.kind = svn_opt_revision_number;
          entry->file_external_rev.value.number = revision;
        }
    }

  return SVN_NO_ERROR;
}


/* Fill in the following fields of ENTRY:

     REVISION
     REPOS
     UUID
     CMT_REV
     CMT_DATE
     CMT_AUTHOR
     DEPTH
     DELETED

   Return: KIND, REPOS_RELPATH, CHECKSUM
*/
static svn_error_t *
get_info_for_deleted(svn_wc_entry_t *entry,
                     svn_node_kind_t *kind,
                     const char **repos_relpath,
                     const svn_checksum_t **checksum,
                     svn_wc__db_lock_t **lock,
                     svn_wc__db_t *db,
                     const char *entry_abspath,
                     const svn_wc_entry_t *parent_entry,
                     svn_boolean_t have_base,
                     svn_boolean_t have_more_work,
                     apr_pool_t *result_pool,
                     apr_pool_t *scratch_pool)
{
  if (have_base && !have_more_work)
    {
      /* This is the delete of a BASE node */
      SVN_ERR(svn_wc__db_base_get_info(NULL, kind,
                                       &entry->revision,
                                       repos_relpath,
                                       &entry->repos,
                                       &entry->uuid,
                                       &entry->cmt_rev,
                                       &entry->cmt_date,
                                       &entry->cmt_author,
                                       &entry->depth,
                                       checksum,
                                       NULL,
                                       lock,
                                       &entry->has_props, NULL,
                                       NULL,
                                       db,
                                       entry_abspath,
                                       result_pool,
                                       scratch_pool));
    }
  else
    {
      const char *work_del_abspath;
      const char *parent_repos_relpath;
      const char *parent_abspath;

      /* This is a deleted child of a copy/move-here,
         so we need to scan up the WORKING tree to find the root of
         the deletion. Then examine its parent to discover its
         future location in the repository.  */
      SVN_ERR(svn_wc__db_read_pristine_info(NULL, kind,
                                            &entry->cmt_rev,
                                            &entry->cmt_date,
                                            &entry->cmt_author,
                                            &entry->depth,
                                            checksum,
                                            NULL,
                                            &entry->has_props, NULL,
                                            db,
                                            entry_abspath,
                                            result_pool,
                                            scratch_pool));
      /* working_size and text_time unavailable */

     SVN_ERR(svn_wc__db_scan_deletion(NULL,
                                      NULL,
                                      &work_del_abspath, NULL,
                                      db, entry_abspath,
                                      scratch_pool, scratch_pool));

      SVN_ERR_ASSERT(work_del_abspath != NULL);
      parent_abspath = svn_dirent_dirname(work_del_abspath, scratch_pool);

      /* The parent directory of the delete root must be added, so we
         can find the required information there */
      SVN_ERR(svn_wc__db_scan_addition(NULL, NULL,
                                       &parent_repos_relpath,
                                       &entry->repos,
                                       &entry->uuid,
                                       NULL, NULL, NULL, NULL,
                                       db, parent_abspath,
                                       result_pool, scratch_pool));

      /* Now glue it all together */
      *repos_relpath = svn_relpath_join(parent_repos_relpath,
                                        svn_dirent_is_child(parent_abspath,
                                                            entry_abspath,
                                                            NULL),
                                        result_pool);


      /* Even though this is the delete of a WORKING node, there might still
         be a BASE node somewhere below with an interesting revision */
      if (have_base)
        {
          svn_wc__db_status_t status;
          SVN_ERR(svn_wc__db_base_get_info(&status, NULL, &entry->revision,
                                           NULL, NULL, NULL, NULL, NULL, NULL,
                                           NULL, NULL, NULL, lock, NULL, NULL,
                                           NULL,
                                           db, entry_abspath,
                                           result_pool, scratch_pool));

          if (status == svn_wc__db_status_not_present)
            entry->deleted = TRUE;
        }
    }

  /* Do some extra work for the child nodes.  */
  if (!SVN_IS_VALID_REVNUM(entry->revision) && parent_entry != NULL)
    {
      /* For child nodes without a revision, pick up the parent's
         revision.  */
      entry->revision = parent_entry->revision;
    }

  return SVN_NO_ERROR;
}


/*
 * Encode tree conflict descriptions into a single string.
 *
 * Set *CONFLICT_DATA to a string, allocated in POOL, that encodes the tree
 * conflicts in CONFLICTS in a form suitable for storage in a single string
 * field in a WC entry. CONFLICTS is a hash of zero or more pointers to
 * svn_wc_conflict_description2_t objects, index by their basenames. All of the
 * conflict victim paths must be siblings.
 *
 * Do all allocations in POOL.
 *
 * @see svn_wc__read_tree_conflicts()
 */
static svn_error_t *
write_tree_conflicts(const char **conflict_data,
                     apr_hash_t *conflicts,
                     apr_pool_t *pool)
{
  svn_skel_t *skel = svn_skel__make_empty_list(pool);
  apr_hash_index_t *hi;

  for (hi = apr_hash_first(pool, conflicts); hi; hi = apr_hash_next(hi))
    {
      svn_skel_t *c_skel;

      SVN_ERR(svn_wc__serialize_conflict(&c_skel, svn__apr_hash_index_val(hi),
                                         pool, pool));
      svn_skel__prepend(c_skel, skel);
    }

  *conflict_data = svn_skel__unparse(skel, pool)->data;

  return SVN_NO_ERROR;
}


/* Read one entry from wc_db. It will be allocated in RESULT_POOL and
   returned in *NEW_ENTRY.

   DIR_ABSPATH is the name of the directory to read this entry from, and
   it will be named NAME (use "" for "this dir").

   DB specifies the wc_db database, and WC_ID specifies which working copy
   this information is being read from.

   If this node is "this dir", then PARENT_ENTRY should be NULL. Otherwise,
   it should refer to the entry for the child's parent directory.

   Temporary allocations are made in SCRATCH_POOL.  */
static svn_error_t *
read_one_entry(const svn_wc_entry_t **new_entry,
               svn_wc__db_t *db,
               apr_int64_t wc_id,
               const char *dir_abspath,
               const char *name,
               const svn_wc_entry_t *parent_entry,
               apr_pool_t *result_pool,
               apr_pool_t *scratch_pool)
{
  svn_node_kind_t kind;
  svn_wc__db_status_t status;
  svn_wc__db_lock_t *lock;
  const char *repos_relpath;
  const svn_checksum_t *checksum;
  svn_filesize_t translated_size;
  svn_wc_entry_t *entry = alloc_entry(result_pool);
  const char *entry_abspath;
  const char *original_repos_relpath;
  const char *original_root_url;
  svn_boolean_t conflicted;
  svn_boolean_t have_base;
  svn_boolean_t have_more_work;

  entry->name = name;

  entry_abspath = svn_dirent_join(dir_abspath, entry->name, scratch_pool);

  SVN_ERR(svn_wc__db_read_info(
            &status,
            &kind,
            &entry->revision,
            &repos_relpath,
            &entry->repos,
            &entry->uuid,
            &entry->cmt_rev,
            &entry->cmt_date,
            &entry->cmt_author,
            &entry->depth,
            &checksum,
            NULL,
            &original_repos_relpath,
            &original_root_url,
            NULL,
            &entry->copyfrom_rev,
            &lock,
            &translated_size,
            &entry->text_time,
            &entry->changelist,
            &conflicted,
            NULL /* op_root */,
            &entry->has_props /* have_props */,
            &entry->has_prop_mods /* props_mod */,
            &have_base,
            &have_more_work,
            NULL /* have_work */,
            db,
            entry_abspath,
            result_pool,
            scratch_pool));

  if (entry->has_prop_mods)
    entry->has_props = TRUE;

  if (strcmp(entry->name, SVN_WC_ENTRY_THIS_DIR) == 0)
    {
      /* get the tree conflict data. */
      apr_hash_t *tree_conflicts = NULL;
      const apr_array_header_t *conflict_victims;
      int k;

      SVN_ERR(svn_wc__db_read_conflict_victims(&conflict_victims, db,
                                               dir_abspath,
                                               scratch_pool,
                                               scratch_pool));

      for (k = 0; k < conflict_victims->nelts; k++)
        {
          int j;
          const apr_array_header_t *child_conflicts;
          const char *child_name;
          const char *child_abspath;

          child_name = APR_ARRAY_IDX(conflict_victims, k, const char *);
          child_abspath = svn_dirent_join(dir_abspath, child_name,
                                          scratch_pool);

          SVN_ERR(svn_wc__read_conflicts(&child_conflicts,
                                         db, child_abspath,
                                         FALSE /* create tempfiles */,
                                         scratch_pool, scratch_pool));

          for (j = 0; j < child_conflicts->nelts; j++)
            {
              const svn_wc_conflict_description2_t *conflict =
                APR_ARRAY_IDX(child_conflicts, j,
                              svn_wc_conflict_description2_t *);

              if (conflict->kind == svn_wc_conflict_kind_tree)
                {
                  if (!tree_conflicts)
                    tree_conflicts = apr_hash_make(scratch_pool);
                  svn_hash_sets(tree_conflicts, child_name, conflict);
                }
            }
        }

      if (tree_conflicts)
        {
          SVN_ERR(write_tree_conflicts(&entry->tree_conflict_data,
                                       tree_conflicts, result_pool));
        }
    }

  if (status == svn_wc__db_status_normal
      || status == svn_wc__db_status_incomplete)
    {
      /* Plain old BASE node.  */
      entry->schedule = svn_wc_schedule_normal;

      /* Grab inherited repository information, if necessary. */
      if (repos_relpath == NULL)
        {
          SVN_ERR(svn_wc__db_scan_base_repos(&repos_relpath,
                                             &entry->repos,
                                             &entry->uuid,
                                             db,
                                             entry_abspath,
                                             result_pool,
                                             scratch_pool));
        }

      entry->incomplete = (status == svn_wc__db_status_incomplete);
    }
  else if (status == svn_wc__db_status_deleted)
    {
      svn_node_kind_t path_kind;

      /* ### we don't have to worry about moves, so this is a delete. */
      entry->schedule = svn_wc_schedule_delete;

      /* If there are multiple working layers or no BASE layer, then
         this is a WORKING delete or WORKING not-present. */
      if (have_more_work || !have_base)
        entry->copied = TRUE;
      else if (have_base && !have_more_work)
        entry->copied = FALSE;
      else
        {
          const char *work_del_abspath;
          SVN_ERR(svn_wc__db_scan_deletion(NULL, NULL,
                                           &work_del_abspath, NULL,
                                           db, entry_abspath,
                                           scratch_pool, scratch_pool));

          if (work_del_abspath)
            entry->copied = TRUE;
        }

      /* If there is still a directory on-disk we keep it, if not it is
         already deleted. Simple, isn't it?

         Before single-db we had to keep the administative area alive until
         after the commit really deletes it. Setting keep alive stopped the
         commit processing from deleting the directory. We don't delete it
         any more, so all we have to do is provide some 'sane' value.
       */
      SVN_ERR(svn_io_check_path(entry_abspath, &path_kind, scratch_pool));
      entry->keep_local = (path_kind == svn_node_dir);
    }
  else if (status == svn_wc__db_status_added)
    {
      svn_wc__db_status_t work_status;
      const char *op_root_abspath;
      const char *scanned_original_relpath;
      svn_revnum_t original_revision;

      /* For child nodes, pick up the parent's revision.  */
      if (*entry->name != '\0')
        {
          assert(parent_entry != NULL);
          assert(entry->revision == SVN_INVALID_REVNUM);

          entry->revision = parent_entry->revision;
        }

      if (have_base)
        {
          svn_wc__db_status_t base_status;

          /* ENTRY->REVISION is overloaded. When a node is schedule-add
             or -replace, then REVISION refers to the BASE node's revision
             that is being overwritten. We need to fetch it now.  */
          SVN_ERR(svn_wc__db_base_get_info(&base_status, NULL,
                                           &entry->revision,
                                           NULL, NULL, NULL,
                                           NULL, NULL, NULL,
                                           NULL, NULL, NULL,
                                           NULL, NULL, NULL, NULL,
                                           db, entry_abspath,
                                           scratch_pool,
                                           scratch_pool));

          if (base_status == svn_wc__db_status_not_present)
            {
              /* The underlying node is DELETED in this revision.  */
              entry->deleted = TRUE;

              /* This is an add since there isn't a node to replace.  */
              entry->schedule = svn_wc_schedule_add;
            }
          else
            entry->schedule = svn_wc_schedule_replace;
        }
      else
        {
          /* There is NO 'not-present' BASE_NODE for this node.
             Therefore, we are looking at some kind of add/copy
             rather than a replace.  */

          /* ### if this looks like a plain old add, then rev=0.  */
          if (!SVN_IS_VALID_REVNUM(entry->copyfrom_rev)
              && !SVN_IS_VALID_REVNUM(entry->cmt_rev))
            entry->revision = 0;

          entry->schedule = svn_wc_schedule_add;
        }

      /* If we don't have "real" data from the entry (obstruction),
         then we cannot begin a scan for data. The original node may
         have important data. Set up stuff to kill that idea off,
         and finish up this entry.  */
        {
          SVN_ERR(svn_wc__db_scan_addition(&work_status,
                                           &op_root_abspath,
                                           &repos_relpath,
                                           &entry->repos,
                                           &entry->uuid,
                                           &scanned_original_relpath,
                                           NULL, NULL, /* original_root|uuid */
                                           &original_revision,
                                           db,
                                           entry_abspath,
                                           result_pool, scratch_pool));

          /* In wc.db we want to keep the valid revision of the not-present
             BASE_REV, but when we used entries we set the revision to 0
             when adding a new node over a not present base node. */
          if (work_status == svn_wc__db_status_added
              && entry->deleted)
            entry->revision = 0;
        }

      if (!SVN_IS_VALID_REVNUM(entry->cmt_rev)
          && scanned_original_relpath == NULL)
        {
          /* There is NOT a last-changed revision (last-changed date and
             author may be unknown, but we can always check the rev).
             The absence of a revision implies this node was added WITHOUT
             any history. Avoid the COPIED checks in the else block.  */
          /* ### scan_addition may need to be updated to avoid returning
             ### status_copied in this case.  */
        }
      /* For backwards-compatiblity purposes we treat moves just like
       * regular copies. */
      else if (work_status == svn_wc__db_status_copied ||
               work_status == svn_wc__db_status_moved_here)
        {
          entry->copied = TRUE;

          /* If this is a child of a copied subtree, then it should be
             schedule_normal.  */
          if (original_repos_relpath == NULL)
            {
              /* ### what if there is a BASE node under there? */
              entry->schedule = svn_wc_schedule_normal;
            }

          /* Copied nodes need to mirror their copyfrom_rev, if they
             don't have a revision of their own already. */
          if (!SVN_IS_VALID_REVNUM(entry->revision)
              || entry->revision == 0 /* added */)
            entry->revision = original_revision;
        }

      /* Does this node have copyfrom_* information?  */
      if (scanned_original_relpath != NULL)
        {
          svn_boolean_t is_copied_child;
          svn_boolean_t is_mixed_rev = FALSE;

          SVN_ERR_ASSERT(work_status == svn_wc__db_status_copied ||
                         work_status == svn_wc__db_status_moved_here);

          /* If this node inherits copyfrom information from an
             ancestor node, then it must be a copied child.  */
          is_copied_child = (original_repos_relpath == NULL);

          /* If this node has copyfrom information on it, then it may
             be an actual copy-root, or it could be participating in
             a mixed-revision copied tree. So if we don't already know
             this is a copied child, then we need to look for this
             mixed-revision situation.  */
          if (!is_copied_child)
            {
              const char *parent_abspath;
              svn_error_t *err;
              const char *parent_repos_relpath;
              const char *parent_root_url;

              /* When we insert entries into the database, we will
                 construct additional copyfrom records for mixed-revision
                 copies. The old entries would simply record the different
                 revision in the entry->revision field. That is not
                 available within wc-ng, so additional copies are made
                 (see the logic inside write_entry()). However, when
                 reading these back *out* of the database, the additional
                 copies look like new "Added" nodes rather than a simple
                 mixed-rev working copy.

                 That would be a behavior change if we did not compensate.
                 If there is copyfrom information for this node, then the
                 code below looks at the parent to detect if it *also* has
                 copyfrom information, and if the copyfrom_url would align
                 properly. If it *does*, then we omit storing copyfrom_url
                 and copyfrom_rev (ie. inherit the copyfrom info like a
                 normal child), and update entry->revision with the
                 copyfrom_rev in order to (re)create the mixed-rev copied
                 subtree that was originally presented for storage.  */

              /* Get the copyfrom information from our parent.

                 Note that the parent could be added/copied/moved-here.
                 There is no way for it to be deleted/moved-away and
                 have *this* node appear as copied.  */
              parent_abspath = svn_dirent_dirname(entry_abspath,
                                                  scratch_pool);
              err = svn_wc__db_scan_addition(NULL,
                                             &op_root_abspath,
                                             NULL, NULL, NULL,
                                             &parent_repos_relpath,
                                             &parent_root_url,
                                             NULL, NULL,
                                             db, parent_abspath,
                                             scratch_pool,
                                             scratch_pool);
              if (err)
                {
                  if (err->apr_err != SVN_ERR_WC_PATH_NOT_FOUND)
                    return svn_error_trace(err);
                  svn_error_clear(err);
                }
              else if (parent_root_url != NULL
                       && strcmp(original_root_url, parent_root_url) == 0)
                {
                  const char *relpath_to_entry = svn_dirent_is_child(
                    op_root_abspath, entry_abspath, NULL);
                  const char *entry_repos_relpath = svn_relpath_join(
                    parent_repos_relpath, relpath_to_entry, scratch_pool);

                  /* The copyfrom repos roots matched.

                     Now we look to see if the copyfrom path of the parent
                     would align with our own path. If so, then it means
                     this copyfrom was spontaneously created and inserted
                     for mixed-rev purposes and can be eliminated without
                     changing the semantics of a mixed-rev copied subtree.

                     See notes/api-errata/wc003.txt for some additional
                     detail, and potential issues.  */
                  if (strcmp(entry_repos_relpath,
                             original_repos_relpath) == 0)
                    {
                      is_copied_child = TRUE;
                      is_mixed_rev = TRUE;
                    }
                }
            }

          if (is_copied_child)
            {
              /* We won't be settig the  copyfrom_url, yet need to
                 clear out the copyfrom_rev. Thus, this node becomes a
                 child of a copied subtree (rather than its own root).  */
              entry->copyfrom_rev = SVN_INVALID_REVNUM;

              /* Children in a copied subtree are schedule normal
                 since we don't plan to actually *do* anything with
                 them. Their operation is implied by ancestors.  */
              entry->schedule = svn_wc_schedule_normal;

              /* And *finally* we turn this entry into the mixed
                 revision node that it was intended to be. This
                 node's revision is taken from the copyfrom record
                 that we spontaneously constructed.  */
              if (is_mixed_rev)
                entry->revision = original_revision;
            }
          else if (original_repos_relpath != NULL)
            {
              entry->copyfrom_url =
                svn_path_url_add_component2(original_root_url,
                                            original_repos_relpath,
                                            result_pool);
            }
          else
            {
              /* NOTE: if original_repos_relpath == NULL, then the
                 second call to scan_addition() will not have occurred.
                 Thus, this use of OP_ROOT_ABSPATH still contains the
                 original value where we fetched a value for
                 SCANNED_REPOS_RELPATH.  */
              const char *relpath_to_entry = svn_dirent_is_child(
                op_root_abspath, entry_abspath, NULL);
              const char *entry_repos_relpath = svn_relpath_join(
                scanned_original_relpath, relpath_to_entry, scratch_pool);

              entry->copyfrom_url =
                svn_path_url_add_component2(original_root_url,
                                            entry_repos_relpath,
                                            result_pool);
            }
        }
    }
  else if (status == svn_wc__db_status_not_present)
    {
      /* ### buh. 'deleted' nodes are actually supposed to be
         ### schedule "normal" since we aren't going to actually *do*
         ### anything to this node at commit time.  */
      entry->schedule = svn_wc_schedule_normal;
      entry->deleted = TRUE;
    }
  else if (status == svn_wc__db_status_server_excluded)
    {
      entry->absent = TRUE;
    }
  else if (status == svn_wc__db_status_excluded)
    {
      entry->schedule = svn_wc_schedule_normal;
      entry->depth = svn_depth_exclude;
    }
  else
    {
      /* ### we should have handled all possible status values.  */
      SVN_ERR_MALFUNCTION();
    }

  /* ### higher levels want repos information about deleted nodes, even
     ### tho they are not "part of" a repository any more.  */
  if (entry->schedule == svn_wc_schedule_delete)
    {
      SVN_ERR(get_info_for_deleted(entry,
                                   &kind,
                                   &repos_relpath,
                                   &checksum,
                                   &lock,
                                   db, entry_abspath,
                                   parent_entry,
                                   have_base, have_more_work,
                                   result_pool, scratch_pool));
    }

  /* ### default to the infinite depth if we don't know it. */
  if (entry->depth == svn_depth_unknown)
    entry->depth = svn_depth_infinity;

  if (kind == svn_node_dir)
    entry->kind = svn_node_dir;
  else if (kind == svn_node_file)
    entry->kind = svn_node_file;
  else if (kind == svn_node_symlink)
    entry->kind = svn_node_file;  /* ### no symlink kind */
  else
    entry->kind = svn_node_unknown;

  /* We should always have a REPOS_RELPATH, except for:
     - deleted nodes
     - certain obstructed nodes
     - not-present nodes
     - absent nodes
     - excluded nodes

     ### the last three should probably have an "implied" REPOS_RELPATH
  */
  SVN_ERR_ASSERT(repos_relpath != NULL
                 || entry->schedule == svn_wc_schedule_delete
                 || status == svn_wc__db_status_not_present
                 || status == svn_wc__db_status_server_excluded
                 || status == svn_wc__db_status_excluded);
  if (repos_relpath)
    entry->url = svn_path_url_add_component2(entry->repos,
                                             repos_relpath,
                                             result_pool);

  if (checksum)
    {
      /* We got a SHA-1, get the corresponding MD-5. */
      if (checksum->kind != svn_checksum_md5)
        SVN_ERR(svn_wc__db_pristine_get_md5(&checksum, db,
                                            entry_abspath, checksum,
                                            scratch_pool, scratch_pool));

      SVN_ERR_ASSERT(checksum->kind == svn_checksum_md5);
      entry->checksum = svn_checksum_to_cstring(checksum, result_pool);
    }

  if (conflicted)
    {
      svn_skel_t *conflict;
      svn_boolean_t text_conflicted;
      svn_boolean_t prop_conflicted;
      SVN_ERR(svn_wc__db_read_conflict(&conflict, db, entry_abspath,
                                       scratch_pool, scratch_pool));

      SVN_ERR(svn_wc__conflict_read_info(NULL, NULL, &text_conflicted,
                                         &prop_conflicted, NULL,
                                         db, dir_abspath, conflict,
                                         scratch_pool, scratch_pool));

      if (text_conflicted)
        {
          const char *my_abspath;
          const char *their_old_abspath;
          const char *their_abspath;
          SVN_ERR(svn_wc__conflict_read_text_conflict(&my_abspath,
                                                      &their_old_abspath,
                                                      &their_abspath,
                                                      db, dir_abspath,
                                                      conflict, scratch_pool,
                                                      scratch_pool));

          if (my_abspath)
            entry->conflict_wrk = svn_dirent_basename(my_abspath, result_pool);

          if (their_old_abspath)
            entry->conflict_old = svn_dirent_basename(their_old_abspath,
                                                      result_pool);

          if (their_abspath)
            entry->conflict_new = svn_dirent_basename(their_abspath,
                                                      result_pool);
        }

      if (prop_conflicted)
        {
          const char *prej_abspath;

          SVN_ERR(svn_wc__conflict_read_prop_conflict(&prej_abspath, NULL,
                                                      NULL, NULL, NULL,
                                                      db, dir_abspath,
                                                      conflict, scratch_pool,
                                                      scratch_pool));

          if (prej_abspath)
            entry->prejfile = svn_dirent_basename(prej_abspath, result_pool);
        }
    }

  if (lock)
    {
      entry->lock_token = lock->token;
      entry->lock_owner = lock->owner;
      entry->lock_comment = lock->comment;
      entry->lock_creation_date = lock->date;
    }

  /* Let's check for a file external.  ugh.  */
  if (status == svn_wc__db_status_normal
      && kind == svn_node_file)
    SVN_ERR(check_file_external(entry, db, entry_abspath, dir_abspath,
                                result_pool, scratch_pool));

  entry->working_size = translated_size;

  *new_entry = entry;

  return SVN_NO_ERROR;
}

/* Read entries for PATH/LOCAL_ABSPATH from DB. The entries
   will be allocated in RESULT_POOL, with temporary allocations in
   SCRATCH_POOL. The entries are returned in RESULT_ENTRIES.  */
static svn_error_t *
read_entries_new(apr_hash_t **result_entries,
                 svn_wc__db_t *db,
                 const char *local_abspath,
                 apr_pool_t *result_pool,
                 apr_pool_t *scratch_pool)
{
  apr_hash_t *entries;
  const apr_array_header_t *children;
  apr_pool_t *iterpool = svn_pool_create(scratch_pool);
  int i;
  const svn_wc_entry_t *parent_entry;
  apr_int64_t wc_id = 1;  /* ### hacky. should remove.  */

  entries = apr_hash_make(result_pool);

  SVN_ERR(read_one_entry(&parent_entry, db, wc_id, local_abspath,
                         "" /* name */,
                         NULL /* parent_entry */,
                         result_pool, iterpool));
  svn_hash_sets(entries, "", parent_entry);

  /* Use result_pool so that the child names (used by reference, rather
     than copied) appear in result_pool.  */
  SVN_ERR(svn_wc__db_read_children(&children, db,
                                   local_abspath,
                                   result_pool, iterpool));
  for (i = children->nelts; i--; )
    {
      const char *name = APR_ARRAY_IDX(children, i, const char *);
      const svn_wc_entry_t *entry;

      svn_pool_clear(iterpool);

      SVN_ERR(read_one_entry(&entry,
                             db, wc_id, local_abspath, name, parent_entry,
                             result_pool, iterpool));
      svn_hash_sets(entries, entry->name, entry);
    }

  svn_pool_destroy(iterpool);

  *result_entries = entries;

  return SVN_NO_ERROR;
}


/* Read a pair of entries from wc_db in the directory DIR_ABSPATH. Return
   the directory's entry in *PARENT_ENTRY and NAME's entry in *ENTRY. The
   two returned pointers will be the same if NAME=="" ("this dir").

   The parent entry must exist.

   The requested entry MAY exist. If it does not, then NULL will be returned.

   The resulting entries are allocated in RESULT_POOL, and all temporary
   allocations are made in SCRATCH_POOL.  */
static svn_error_t *
read_entry_pair(const svn_wc_entry_t **parent_entry,
                const svn_wc_entry_t **entry,
                svn_wc__db_t *db,
                const char *dir_abspath,
                const char *name,
                apr_pool_t *result_pool,
                apr_pool_t *scratch_pool)
{
  apr_int64_t wc_id = 1;  /* ### hacky. should remove.  */

  SVN_ERR(read_one_entry(parent_entry, db, wc_id, dir_abspath,
                         "" /* name */,
                         NULL /* parent_entry */,
                         result_pool, scratch_pool));

  /* If we need the entry for "this dir", then return the parent_entry
     in both outputs. Otherwise, read the child node.  */
  if (*name == '\0')
    {
      /* If the retrieved node is a FILE, then we have a problem. We asked
         for a directory. This implies there is an obstructing, unversioned
         directory where a FILE should be. We navigated from the obstructing
         subdir up to the parent dir, then returned the FILE found there.

         Let's return WC_MISSING cuz the caller thought we had a dir, but
         that (versioned subdir) isn't there.  */
      if ((*parent_entry)->kind == svn_node_file)
        {
          *parent_entry = NULL;
          return svn_error_createf(SVN_ERR_WC_MISSING, NULL,
                                 _("'%s' is not a versioned working copy"),
                                 svn_dirent_local_style(dir_abspath,
                                                        scratch_pool));
        }

      *entry = *parent_entry;
    }
  else
    {
      const apr_array_header_t *children;
      int i;

      /* Default to not finding the child.  */
      *entry = NULL;

      /* Determine whether the parent KNOWS about this child. If it does
         not, then we should not attempt to look for it.

         For example: the parent doesn't "know" about the child, but the
         versioned directory *does* exist on disk. We don't want to look
         into that subdir.  */
      SVN_ERR(svn_wc__db_read_children(&children, db, dir_abspath,
                                       scratch_pool, scratch_pool));
      for (i = children->nelts; i--; )
        {
          const char *child = APR_ARRAY_IDX(children, i, const char *);

          if (strcmp(child, name) == 0)
            {
              svn_error_t *err;

              err = read_one_entry(entry,
                                   db, wc_id, dir_abspath, name, *parent_entry,
                                   result_pool, scratch_pool);
              if (err)
                {
                  if (err->apr_err != SVN_ERR_WC_PATH_NOT_FOUND)
                    return svn_error_trace(err);

                  /* No problem. Clear the error and leave the default value
                     of "missing".  */
                  svn_error_clear(err);
                }

              /* Found it. No need to keep searching.  */
              break;
            }
        }
      /* if the loop ends without finding a child, then we have the default
         ENTRY value of NULL.  */
    }

  return SVN_NO_ERROR;
}


/* */
static svn_error_t *
read_entries(apr_hash_t **entries,
             svn_wc__db_t *db,
             const char *wcroot_abspath,
             apr_pool_t *result_pool,
             apr_pool_t *scratch_pool)
{
  int wc_format;

  SVN_ERR(svn_wc__db_temp_get_format(&wc_format, db, wcroot_abspath,
                                     scratch_pool));

  if (wc_format < SVN_WC__WC_NG_VERSION)
    return svn_error_trace(svn_wc__read_entries_old(entries,
                                                    wcroot_abspath,
                                                    result_pool,
                                                    scratch_pool));

  return svn_error_trace(read_entries_new(entries, db, wcroot_abspath,
                                          result_pool, scratch_pool));
}


/* For a given LOCAL_ABSPATH, using DB, set *ADM_ABSPATH to the directory in
   which the entry information is located, and *ENTRY_NAME to the entry name
   to access that entry.

   KIND is as in svn_wc__get_entry().

   Return the results in RESULT_POOL and use SCRATCH_POOL for temporary
   allocations. */
static svn_error_t *
get_entry_access_info(const char **adm_abspath,
                      const char **entry_name,
                      svn_wc__db_t *db,
                      const char *local_abspath,
                      svn_node_kind_t kind,
                      apr_pool_t *result_pool,
                      apr_pool_t *scratch_pool)
{
  svn_wc_adm_access_t *adm_access;
  svn_boolean_t read_from_subdir = FALSE;

  /* If the caller didn't know the node kind, then stat the path. Maybe
     it is really there, and we can speed up the steps below.  */
  if (kind == svn_node_unknown)
    {
      svn_node_kind_t on_disk;

      /* Do we already have an access baton for LOCAL_ABSPATH?  */
      adm_access = svn_wc__adm_retrieve_internal2(db, local_abspath,
                                                  scratch_pool);
      if (adm_access)
        {
          /* Sweet. The node is a directory.  */
          on_disk = svn_node_dir;
        }
      else
        {
          svn_boolean_t special;

          /* What's on disk?  */
          SVN_ERR(svn_io_check_special_path(local_abspath, &on_disk, &special,
                                            scratch_pool));
        }

      if (on_disk != svn_node_dir)
        {
          /* If this is *anything* besides a directory (FILE, NONE, or
             UNKNOWN), then we cannot treat it as a versioned directory
             containing entries to read. Leave READ_FROM_SUBDIR as FALSE,
             so that the parent will be examined.

             For NONE and UNKNOWN, it may be that metadata exists for the
             node, even though on-disk is unhelpful.

             If NEED_PARENT_STUB is TRUE, and the entry is not a DIRECTORY,
             then we'll error.

             If NEED_PARENT_STUB if FALSE, and we successfully read a stub,
             then this on-disk node is obstructing the read.  */
        }
      else
        {
          /* We found a directory for this UNKNOWN node. Determine whether
             we need to read inside it.  */
          read_from_subdir = TRUE;
        }
    }
  else if (kind == svn_node_dir)
    {
      read_from_subdir = TRUE;
    }

  if (read_from_subdir)
    {
      /* KIND must be a DIR or UNKNOWN (and we found a subdir). We want
         the "real" data, so treat LOCAL_ABSPATH as a versioned directory.  */
      *adm_abspath = apr_pstrdup(result_pool, local_abspath);
      *entry_name = "";
    }
  else
    {
      /* FILE node needs to read the parent directory. Or a DIR node
         needs to read from the parent to get at the stub entry. Or this
         is an UNKNOWN node, and we need to examine the parent.  */
      svn_dirent_split(adm_abspath, entry_name, local_abspath, result_pool);
    }

  return SVN_NO_ERROR;
}


svn_error_t *
svn_wc__get_entry(const svn_wc_entry_t **entry,
                  svn_wc__db_t *db,
                  const char *local_abspath,
                  svn_boolean_t allow_unversioned,
                  svn_node_kind_t kind,
                  apr_pool_t *result_pool,
                  apr_pool_t *scratch_pool)
{
  const char *dir_abspath;
  const char *entry_name;

  SVN_ERR(get_entry_access_info(&dir_abspath, &entry_name, db, local_abspath,
                                kind, scratch_pool, scratch_pool));

    {
      const svn_wc_entry_t *parent_entry;
      svn_error_t *err;

      /* NOTE: if KIND is UNKNOWN and we decided to examine the *parent*
         directory, then it is possible we moved out of the working copy.
         If the on-disk node is a DIR, and we asked for a stub, then we
         obviously can't provide that (parent has no info). If the on-disk
         node is a FILE/NONE/UNKNOWN, then it is obstructing the real
         LOCAL_ABSPATH (or it was never a versioned item). In all these
         cases, the read_entries() will (properly) throw an error.

         NOTE: if KIND is a DIR and we asked for the real data, but it is
         obstructed on-disk by some other node kind (NONE, FILE, UNKNOWN),
         then this will throw an error.  */

      err = read_entry_pair(&parent_entry, entry,
                            db, dir_abspath, entry_name,
                            result_pool, scratch_pool);
      if (err)
        {
          if (err->apr_err != SVN_ERR_WC_MISSING || kind != svn_node_unknown
              || *entry_name != '\0')
            return svn_error_trace(err);
          svn_error_clear(err);

          /* The caller didn't know the node type, we saw a directory there,
             we attempted to read IN that directory, and then wc_db reports
             that it is NOT a working copy directory. It is possible that
             one of two things has happened:

             1) a directory is obstructing a file in the parent
             2) the (versioned) directory's contents have been removed

             Let's assume situation (1); if that is true, then we can just
             return the newly-found data.

             If we assumed (2), then a valid result still won't help us
             since the caller asked for the actual contents, not the stub
             (which is why we read *into* the directory). However, if we
             assume (1) and get back a stub, then we have verified a
             missing, versioned directory, and can return an error
             describing that.

             Redo the fetch, but "insist" we are trying to find a file.
             This will read from the parent directory of the "file".  */
          err = svn_wc__get_entry(entry, db, local_abspath, allow_unversioned,
                                  svn_node_file, result_pool, scratch_pool);
          if (err == SVN_NO_ERROR)
            return SVN_NO_ERROR;
          if (err->apr_err != SVN_ERR_NODE_UNEXPECTED_KIND)
            return svn_error_trace(err);
          svn_error_clear(err);

          /* We asked for a FILE, but the node found is a DIR. Thus, we
             are looking at a stub. Originally, we tried to read into the
             subdir because NEED_PARENT_STUB is FALSE. The stub we just
             read is not going to work for the caller, so inform them of
             the missing subdirectory.  */
          SVN_ERR_ASSERT(*entry != NULL && (*entry)->kind == svn_node_dir);
          return svn_error_createf(SVN_ERR_WC_PATH_NOT_FOUND, NULL,
                                 _("Admin area of '%s' is missing"),
                                 svn_dirent_local_style(local_abspath,
                                                        scratch_pool));
        }
    }

  if (*entry == NULL)
    {
      if (allow_unversioned)
        return SVN_NO_ERROR;
      return svn_error_createf(SVN_ERR_WC_PATH_NOT_FOUND, NULL,
                               _("'%s' is not under version control"),
                               svn_dirent_local_style(local_abspath,
                                                      scratch_pool));
    }

  /* The caller had the wrong information.  */
  if ((kind == svn_node_file && (*entry)->kind != svn_node_file)
      || (kind == svn_node_dir && (*entry)->kind != svn_node_dir))
    return svn_error_createf(SVN_ERR_NODE_UNEXPECTED_KIND, NULL,
                             _("'%s' is not of the right kind"),
                             svn_dirent_local_style(local_abspath,
                                                    scratch_pool));

  return SVN_NO_ERROR;
}

/* TODO ### Rewrite doc string to mention ENTRIES_ALL; not ADM_ACCESS.

   Prune the deleted entries from the cached entries in ADM_ACCESS, and
   return that collection in *ENTRIES_PRUNED.  SCRATCH_POOL is used for local,
   short term, memory allocation, RESULT_POOL for permanent stuff.  */
static svn_error_t *
prune_deleted(apr_hash_t **entries_pruned,
              apr_hash_t *entries_all,
              apr_pool_t *result_pool,
              apr_pool_t *scratch_pool)
{
  apr_hash_index_t *hi;

  if (!entries_all)
    {
      *entries_pruned = NULL;
      return SVN_NO_ERROR;
    }

  /* I think it will be common for there to be no deleted entries, so
     it is worth checking for that case as we can optimise it. */
  for (hi = apr_hash_first(scratch_pool, entries_all);
       hi;
       hi = apr_hash_next(hi))
    {
      svn_boolean_t hidden;

      SVN_ERR(svn_wc__entry_is_hidden(&hidden,
                                      svn__apr_hash_index_val(hi)));
      if (hidden)
        break;
    }

  if (! hi)
    {
      /* There are no deleted entries, so we can use the full hash */
      *entries_pruned = entries_all;
      return SVN_NO_ERROR;
    }

  /* Construct pruned hash without deleted entries */
  *entries_pruned = apr_hash_make(result_pool);
  for (hi = apr_hash_first(scratch_pool, entries_all);
       hi;
       hi = apr_hash_next(hi))
    {
      const void *key = svn__apr_hash_index_key(hi);
      const svn_wc_entry_t *entry = svn__apr_hash_index_val(hi);
      svn_boolean_t hidden;

      SVN_ERR(svn_wc__entry_is_hidden(&hidden, entry));
      if (!hidden)
        svn_hash_sets(*entries_pruned, key, entry);
    }

  return SVN_NO_ERROR;
}

struct entries_read_baton_t
{
  apr_hash_t **new_entries;
  svn_wc__db_t *db;
  const char *local_abspath;
  apr_pool_t *result_pool;
};

static svn_error_t *
entries_read_txn(void *baton, svn_sqlite__db_t *db, apr_pool_t *scratch_pool)
{
  struct entries_read_baton_t *erb = baton;

  SVN_ERR(read_entries(erb->new_entries, erb->db, erb->local_abspath,
                       erb->result_pool, scratch_pool));

  return NULL;
}

svn_error_t *
svn_wc__entries_read_internal(apr_hash_t **entries,
                              svn_wc_adm_access_t *adm_access,
                              svn_boolean_t show_hidden,
                              apr_pool_t *pool)
{
  apr_hash_t *new_entries;

  new_entries = svn_wc__adm_access_entries(adm_access);
  if (! new_entries)
    {
      svn_wc__db_t *db = svn_wc__adm_get_db(adm_access);
      const char *local_abspath = svn_wc__adm_access_abspath(adm_access);
      apr_pool_t *result_pool = svn_wc__adm_access_pool_internal(adm_access);
      svn_sqlite__db_t *sdb;
      struct entries_read_baton_t erb;

      /* ### Use the borrow DB api to handle all calls in a single read
         ### transaction. This api is used extensively in our test suite
         ### via the entries-read application. */

      SVN_ERR(svn_wc__db_temp_borrow_sdb(&sdb, db, local_abspath, pool));

      erb.db = db;
      erb.local_abspath = local_abspath;
      erb.new_entries = &new_entries;
      erb.result_pool = result_pool;

      SVN_ERR(svn_sqlite__with_lock(sdb, entries_read_txn, &erb, pool));

      svn_wc__adm_access_set_entries(adm_access, new_entries);
    }

  if (show_hidden)
    *entries = new_entries;
  else
    SVN_ERR(prune_deleted(entries, new_entries,
                          svn_wc__adm_access_pool_internal(adm_access),
                          pool));

  return SVN_NO_ERROR;
}

svn_error_t *
svn_wc_entries_read(apr_hash_t **entries,
                    svn_wc_adm_access_t *adm_access,
                    svn_boolean_t show_hidden,
                    apr_pool_t *pool)
{
  return svn_error_trace(svn_wc__entries_read_internal(entries, adm_access,
                                                       show_hidden, pool));
}

/* No transaction required: called from write_entry which is itself
   transaction-wrapped. */
static svn_error_t *
insert_node(svn_sqlite__db_t *sdb,
            const db_node_t *node,
            apr_pool_t *scratch_pool)
{
  svn_sqlite__stmt_t *stmt;

  SVN_ERR_ASSERT(node->op_depth > 0 || node->repos_relpath);

  SVN_ERR(svn_sqlite__get_statement(&stmt, sdb, STMT_INSERT_NODE));
  SVN_ERR(svn_sqlite__bindf(stmt, "isdsnnnnsnrisnnni",
                            node->wc_id,
                            node->local_relpath,
                            node->op_depth,
                            node->parent_relpath,
                            /* Setting depth for files? */
                            svn_depth_to_word(node->depth),
                            node->changed_rev,
                            node->changed_date,
                            node->changed_author,
                            node->recorded_time));

  if (node->repos_relpath)
    {
      SVN_ERR(svn_sqlite__bind_int64(stmt, 5,
                                     node->repos_id));
      SVN_ERR(svn_sqlite__bind_text(stmt, 6,
                                    node->repos_relpath));
      SVN_ERR(svn_sqlite__bind_revnum(stmt, 7, node->revision));
    }

  if (node->presence == svn_wc__db_status_normal)
    SVN_ERR(svn_sqlite__bind_text(stmt, 8, "normal"));
  else if (node->presence == svn_wc__db_status_not_present)
    SVN_ERR(svn_sqlite__bind_text(stmt, 8, "not-present"));
  else if (node->presence == svn_wc__db_status_base_deleted)
    SVN_ERR(svn_sqlite__bind_text(stmt, 8, "base-deleted"));
  else if (node->presence == svn_wc__db_status_incomplete)
    SVN_ERR(svn_sqlite__bind_text(stmt, 8, "incomplete"));
  else if (node->presence == svn_wc__db_status_excluded)
    SVN_ERR(svn_sqlite__bind_text(stmt, 8, "excluded"));
  else if (node->presence == svn_wc__db_status_server_excluded)
    SVN_ERR(svn_sqlite__bind_text(stmt, 8, "server-excluded"));

  if (node->kind == svn_node_none)
    SVN_ERR(svn_sqlite__bind_text(stmt, 10, "unknown"));
  else
    SVN_ERR(svn_sqlite__bind_text(stmt, 10,
                                  svn_node_kind_to_word(node->kind)));

  if (node->kind == svn_node_file)
    {
      if (!node->checksum
          && node->op_depth == 0
          && node->presence != svn_wc__db_status_not_present
          && node->presence != svn_wc__db_status_excluded
          && node->presence != svn_wc__db_status_server_excluded)
        return svn_error_createf(SVN_ERR_WC_CORRUPT, NULL,
                                 _("The file '%s' has no checksum"),
                                 svn_dirent_local_style(node->local_relpath,
                                                        scratch_pool));

      SVN_ERR(svn_sqlite__bind_checksum(stmt, 14, node->checksum,
                                        scratch_pool));
    }

  if (node->properties) /* ### Never set, props done later */
    SVN_ERR(svn_sqlite__bind_properties(stmt, 15, node->properties,
                                        scratch_pool));

  if (node->recorded_size != SVN_INVALID_FILESIZE)
    SVN_ERR(svn_sqlite__bind_int64(stmt, 16, node->recorded_size));

  if (node->file_external)
    SVN_ERR(svn_sqlite__bind_int(stmt, 20, 1));

  if (node->inherited_props)
    SVN_ERR(svn_sqlite__bind_iprops(stmt, 23, node->inherited_props,
                                    scratch_pool));

  SVN_ERR(svn_sqlite__insert(NULL, stmt));

  return SVN_NO_ERROR;
}


/* */
static svn_error_t *
insert_actual_node(svn_sqlite__db_t *sdb,
                   svn_wc__db_t *db,
                   const char *wri_abspath,
                   const db_actual_node_t *actual_node,
                   apr_pool_t *scratch_pool)
{
  svn_sqlite__stmt_t *stmt;
  svn_skel_t *conflict_data = NULL;

  SVN_ERR(svn_sqlite__get_statement(&stmt, sdb, STMT_INSERT_ACTUAL_NODE));

  SVN_ERR(svn_sqlite__bind_int64(stmt, 1, actual_node->wc_id));
  SVN_ERR(svn_sqlite__bind_text(stmt, 2, actual_node->local_relpath));
  SVN_ERR(svn_sqlite__bind_text(stmt, 3, actual_node->parent_relpath));

  if (actual_node->properties)
    SVN_ERR(svn_sqlite__bind_properties(stmt, 4, actual_node->properties,
                                        scratch_pool));

  if (actual_node->changelist)
    SVN_ERR(svn_sqlite__bind_text(stmt, 5, actual_node->changelist));

  SVN_ERR(svn_wc__upgrade_conflict_skel_from_raw(
                                &conflict_data,
                                db, wri_abspath,
                                actual_node->local_relpath,
                                actual_node->conflict_old,
                                actual_node->conflict_working,
                                actual_node->conflict_new,
                                actual_node->prop_reject,
                                actual_node->tree_conflict_data,
                                actual_node->tree_conflict_data
                                    ? strlen(actual_node->tree_conflict_data)
                                    : 0,
                                scratch_pool, scratch_pool));

  if (conflict_data)
    {
      svn_stringbuf_t *data = svn_skel__unparse(conflict_data, scratch_pool);

      SVN_ERR(svn_sqlite__bind_blob(stmt, 6, data->data, data->len));
    }

  /* Execute and reset the insert clause. */
  return svn_error_trace(svn_sqlite__insert(NULL, stmt));
}

static svn_boolean_t
is_switched(db_node_t *parent,
            db_node_t *child,
            apr_pool_t *scratch_pool)
{
  if (parent && child)
    {
      if (parent->repos_id != child->repos_id)
        return TRUE;

      if (parent->repos_relpath && child->repos_relpath)
        {
          const char *unswitched
            = svn_relpath_join(parent->repos_relpath,
                               svn_relpath_basename(child->local_relpath,
                                                    scratch_pool),
                               scratch_pool);
          if (strcmp(unswitched, child->repos_relpath))
            return TRUE;
        }
    }

  return FALSE;
}

struct write_baton {
  db_node_t *base;
  db_node_t *work;
  db_node_t *below_work;
  apr_hash_t *tree_conflicts;
};

#define WRITE_ENTRY_ASSERT(expr) \
  if (!(expr)) \
    return svn_error_createf(SVN_ERR_ASSERTION_FAIL, NULL,  \
                             _("Unable to upgrade '%s' at line %d"),    \
                             svn_dirent_local_style( \
                               svn_dirent_join(root_abspath, \
                                               local_relpath,           \
                                               scratch_pool),           \
                               scratch_pool), __LINE__)

/* Write the information for ENTRY to WC_DB.  The WC_ID, REPOS_ID and
   REPOS_ROOT will all be used for writing ENTRY.
   ### transitioning from straight sql to using the wc_db APIs.  For the
   ### time being, we'll need both parameters. */
static svn_error_t *
write_entry(struct write_baton **entry_node,
            const struct write_baton *parent_node,
            svn_wc__db_t *db,
            svn_sqlite__db_t *sdb,
            apr_int64_t wc_id,
            apr_int64_t repos_id,
            const svn_wc_entry_t *entry,
            const svn_wc__text_base_info_t *text_base_info,
            const char *local_relpath,
            const char *tmp_entry_abspath,
            const char *root_abspath,
            const svn_wc_entry_t *this_dir,
            svn_boolean_t create_locks,
            apr_pool_t *result_pool,
            apr_pool_t *scratch_pool)
{
  db_node_t *base_node = NULL;
  db_node_t *working_node = NULL, *below_working_node = NULL;
  db_actual_node_t *actual_node = NULL;
  const char *parent_relpath;
  apr_hash_t *tree_conflicts;

  if (*local_relpath == '\0')
    parent_relpath = NULL;
  else
    parent_relpath = svn_relpath_dirname(local_relpath, scratch_pool);

  /* This is how it should work, it doesn't work like this yet because
     we need proper op_depth to layer the working nodes.

     Using "svn add", "svn rm", "svn cp" only files can be replaced
     pre-wcng; directories can only be normal, deleted or added.
     Files cannot be replaced within a deleted directory, so replaced
     files can only exist in a normal directory, or a directory that
     is added+copied.  In a normal directory a replaced file needs a
     base node and a working node, in an added+copied directory a
     replaced file needs two working nodes at different op-depths.

     With just the above operations the conversion for files and
     directories is straightforward:

           pre-wcng                             wcng
     parent         child                 parent     child

     normal         normal                base       base
     add+copied     normal+copied         work       work
     normal+copied  normal+copied         work       work
     normal         delete                base       base+work
     delete         delete                base+work  base+work
     add+copied     delete                work       work
     normal         add                   base       work
     add            add                   work       work
     add+copied     add                   work       work
     normal         add+copied            base       work
     add            add+copied            work       work
     add+copied     add+copied            work       work
     normal         replace               base       base+work
     add+copied     replace               work       work+work
     normal         replace+copied        base       base+work
     add+copied     replace+copied        work       work+work

     However "svn merge" make this more complicated.  The pre-wcng
     "svn merge" is capable of replacing a directory, that is it can
     mark the whole tree deleted, and then copy another tree on top.
     The entries then represent the replacing tree overlayed on the
     deleted tree.

       original       replace          schedule in
       tree           tree             combined tree

       A              A                replace+copied
       A/f                             delete+copied
       A/g            A/g              replace+copied
                      A/h              add+copied
       A/B            A/B              replace+copied
       A/B/f                           delete+copied
       A/B/g          A/B/g            replace+copied
                      A/B/h            add+copied
       A/C                             delete+copied
       A/C/f                           delete+copied
                      A/D              add+copied
                      A/D/f            add+copied

     The original tree could be normal tree, or an add+copied tree.
     Committing such a merge generally worked, but making further tree
     modifications before commit sometimes failed.

     The root of the replace is handled like the file replace:

           pre-wcng                             wcng
     parent         child                 parent     child

     normal         replace+copied        base       base+work
     add+copied     replace+copied        work       work+work

     although obviously the node is a directory rather then a file.
     There are then more conversion states where the parent is
     replaced.

           pre-wcng                                wcng
     parent           child              parent            child

     replace+copied   add                [base|work]+work  work
     replace+copied   add+copied         [base|work]+work  work
     replace+copied   delete+copied      [base|work]+work  [base|work]+work
     delete+copied    delete+copied      [base|work]+work  [base|work]+work
     replace+copied   replace+copied     [base|work]+work  [base|work]+work
  */

  WRITE_ENTRY_ASSERT(parent_node || entry->schedule == svn_wc_schedule_normal);

  WRITE_ENTRY_ASSERT(!parent_node || parent_node->base
                     || parent_node->below_work || parent_node->work);

  switch (entry->schedule)
    {
      case svn_wc_schedule_normal:
        if (entry->copied ||
            (entry->depth == svn_depth_exclude
             && parent_node && !parent_node->base && parent_node->work))
          working_node = MAYBE_ALLOC(working_node, result_pool);
        else
          base_node = MAYBE_ALLOC(base_node, result_pool);
        break;

      case svn_wc_schedule_add:
        working_node = MAYBE_ALLOC(working_node, result_pool);
        if (entry->deleted)
          {
            if (parent_node->base)
              base_node = MAYBE_ALLOC(base_node, result_pool);
            else
              below_working_node = MAYBE_ALLOC(below_working_node, result_pool);
          }
        break;

      case svn_wc_schedule_delete:
        working_node = MAYBE_ALLOC(working_node, result_pool);
        if (parent_node->base)
          base_node = MAYBE_ALLOC(base_node, result_pool);
        if (parent_node->work)
          below_working_node = MAYBE_ALLOC(below_working_node, result_pool);
        break;

      case svn_wc_schedule_replace:
        working_node = MAYBE_ALLOC(working_node, result_pool);
        if (parent_node->base)
          base_node = MAYBE_ALLOC(base_node, result_pool);
        else
          below_working_node = MAYBE_ALLOC(below_working_node, result_pool);
        break;
    }

  /* Something deleted in this revision means there should always be a
     BASE node to indicate the not-present node.  */
  if (entry->deleted)
    {
      WRITE_ENTRY_ASSERT(base_node || below_working_node);
      WRITE_ENTRY_ASSERT(!entry->incomplete);
      if (base_node)
        base_node->presence = svn_wc__db_status_not_present;
      else
        below_working_node->presence = svn_wc__db_status_not_present;
    }
  else if (entry->absent)
    {
      WRITE_ENTRY_ASSERT(base_node && !working_node && !below_working_node);
      WRITE_ENTRY_ASSERT(!entry->incomplete);
      base_node->presence = svn_wc__db_status_server_excluded;
    }

  if (entry->copied)
    {
      if (entry->copyfrom_url)
        {
          working_node->repos_id = repos_id;
          working_node->repos_relpath = svn_uri_skip_ancestor(
                                          this_dir->repos, entry->copyfrom_url,
                                          result_pool);
          working_node->revision = entry->copyfrom_rev;
          working_node->op_depth
            = svn_wc__db_op_depth_for_upgrade(local_relpath);
        }
      else if (parent_node->work && parent_node->work->repos_relpath)
        {
          working_node->repos_id = repos_id;
          working_node->repos_relpath
            = svn_relpath_join(parent_node->work->repos_relpath,
                               svn_relpath_basename(local_relpath, NULL),
                               result_pool);
          working_node->revision = parent_node->work->revision;
          working_node->op_depth = parent_node->work->op_depth;
        }
      else if (parent_node->below_work
                && parent_node->below_work->repos_relpath)
        {
          working_node->repos_id = repos_id;
          working_node->repos_relpath
            = svn_relpath_join(parent_node->below_work->repos_relpath,
                               svn_relpath_basename(local_relpath, NULL),
                               result_pool);
          working_node->revision = parent_node->below_work->revision;
          working_node->op_depth = parent_node->below_work->op_depth;
        }
      else
        return svn_error_createf(SVN_ERR_ENTRY_MISSING_URL, NULL,
                                 _("No copyfrom URL for '%s'"),
                                 svn_dirent_local_style(local_relpath,
                                                        scratch_pool));
    }

  if (entry->conflict_old)
    {
      actual_node = MAYBE_ALLOC(actual_node, scratch_pool);
      if (parent_relpath && entry->conflict_old)
        actual_node->conflict_old = svn_relpath_join(parent_relpath,
                                                     entry->conflict_old,
                                                     scratch_pool);
      else
        actual_node->conflict_old = entry->conflict_old;
      if (parent_relpath && entry->conflict_new)
        actual_node->conflict_new = svn_relpath_join(parent_relpath,
                                                     entry->conflict_new,
                                                     scratch_pool);
      else
        actual_node->conflict_new = entry->conflict_new;
      if (parent_relpath && entry->conflict_wrk)
        actual_node->conflict_working = svn_relpath_join(parent_relpath,
                                                         entry->conflict_wrk,
                                                         scratch_pool);
      else
        actual_node->conflict_working = entry->conflict_wrk;
    }

  if (entry->prejfile)
    {
      actual_node = MAYBE_ALLOC(actual_node, scratch_pool);
      actual_node->prop_reject = svn_relpath_join((entry->kind == svn_node_dir
                                                   ? local_relpath
                                                   : parent_relpath),
                                                  entry->prejfile,
                                                  scratch_pool);
    }

  if (entry->changelist)
    {
      actual_node = MAYBE_ALLOC(actual_node, scratch_pool);
      actual_node->changelist = entry->changelist;
    }

  /* ### set the text_mod value? */

  if (entry_node && entry->tree_conflict_data)
    {
      /* Issues #3840/#3916: 1.6 stores multiple tree conflicts on the
         parent node, 1.7 stores them directly on the conflited nodes.
         So "((skel1) (skel2))" becomes "(skel1)" and "(skel2)" */
      svn_skel_t *skel;

      skel = svn_skel__parse(entry->tree_conflict_data,
                             strlen(entry->tree_conflict_data),
                             scratch_pool);
      tree_conflicts = apr_hash_make(result_pool);
      skel = skel->children;
      while(skel)
        {
          svn_wc_conflict_description2_t *conflict;
          svn_skel_t *new_skel;
          const char *key;

          /* *CONFLICT is allocated so it is safe to use a non-const pointer */
          SVN_ERR(svn_wc__deserialize_conflict(
                             (const svn_wc_conflict_description2_t**)&conflict,
                                               skel,
                                               svn_dirent_join(root_abspath,
                                                               local_relpath,
                                                               scratch_pool),
                                               scratch_pool, scratch_pool));

          WRITE_ENTRY_ASSERT(conflict->kind == svn_wc_conflict_kind_tree);

          /* Fix dubious data stored by old clients, local adds don't have
             a repository URL. */
          if (conflict->reason == svn_wc_conflict_reason_added)
            conflict->src_left_version = NULL;

          SVN_ERR(svn_wc__serialize_conflict(&new_skel, conflict,
                                             scratch_pool, scratch_pool));

          /* Store in hash to be retrieved when writing the child
             row. */
          key = svn_dirent_skip_ancestor(root_abspath, conflict->local_abspath);
          svn_hash_sets(tree_conflicts, apr_pstrdup(result_pool, key),
                        svn_skel__unparse(new_skel, result_pool)->data);
          skel = skel->next;
        }
    }
  else
    tree_conflicts = NULL;

  if (parent_node && parent_node->tree_conflicts)
    {
      const char *tree_conflict_data =
          svn_hash_gets(parent_node->tree_conflicts, local_relpath);
      if (tree_conflict_data)
        {
          actual_node = MAYBE_ALLOC(actual_node, scratch_pool);
          actual_node->tree_conflict_data = tree_conflict_data;
        }

      /* Reset hash so that we don't write the row again when writing
         actual-only nodes */
      svn_hash_sets(parent_node->tree_conflicts, local_relpath, NULL);
    }

  if (entry->file_external_path != NULL)
    {
      base_node = MAYBE_ALLOC(base_node, result_pool);
    }


  /* Insert the base node. */
  if (base_node)
    {
      base_node->wc_id = wc_id;
      base_node->local_relpath = local_relpath;
      base_node->op_depth = 0;
      base_node->parent_relpath = parent_relpath;
      base_node->revision = entry->revision;
      base_node->recorded_time = entry->text_time;
      base_node->recorded_size = entry->working_size;

      if (entry->depth != svn_depth_exclude)
        base_node->depth = entry->depth;
      else
        {
          base_node->presence = svn_wc__db_status_excluded;
          base_node->depth = svn_depth_infinity;
        }

      if (entry->deleted)
        {
          WRITE_ENTRY_ASSERT(base_node->presence
                             == svn_wc__db_status_not_present);
          /* ### should be svn_node_unknown, but let's store what we have. */
          base_node->kind = entry->kind;
        }
      else if (entry->absent)
        {
          WRITE_ENTRY_ASSERT(base_node->presence
                             == svn_wc__db_status_server_excluded);
          /* ### should be svn_node_unknown, but let's store what we have. */
          base_node->kind = entry->kind;

          /* Store the most likely revision in the node to avoid
             base nodes without a valid revision. Of course
             we remember that the data is still incomplete. */
          if (!SVN_IS_VALID_REVNUM(base_node->revision) && parent_node->base)
            base_node->revision = parent_node->base->revision;
        }
      else
        {
          base_node->kind = entry->kind;

          if (base_node->presence != svn_wc__db_status_excluded)
            {
              /* All subdirs are initially incomplete, they stop being
                 incomplete when the entries file in the subdir is
                 upgraded and remain incomplete if that doesn't happen. */
              if (entry->kind == svn_node_dir
                  && strcmp(entry->name, SVN_WC_ENTRY_THIS_DIR))
                {
                  base_node->presence = svn_wc__db_status_incomplete;

                  /* Store the most likely revision in the node to avoid
                     base nodes without a valid revision. Of course
                     we remember that the data is still incomplete. */
                  if (parent_node->base)
                    base_node->revision = parent_node->base->revision;
                }
              else if (entry->incomplete)
                {
                  /* ### nobody should have set the presence.  */
                  WRITE_ENTRY_ASSERT(base_node->presence
                                     == svn_wc__db_status_normal);
                  base_node->presence = svn_wc__db_status_incomplete;
                }
            }
        }

      if (entry->kind == svn_node_dir)
        base_node->checksum = NULL;
      else
        {
          if (text_base_info && text_base_info->revert_base.sha1_checksum)
            base_node->checksum = text_base_info->revert_base.sha1_checksum;
          else if (text_base_info && text_base_info->normal_base.sha1_checksum)
            base_node->checksum = text_base_info->normal_base.sha1_checksum;
          else
            base_node->checksum = NULL;

          /* The base MD5 checksum is available in the entry, unless there
           * is a copied WORKING node.  If possible, verify that the entry
           * checksum matches the base file that we found. */
          if (! (working_node && entry->copied))
            {
              svn_checksum_t *entry_md5_checksum, *found_md5_checksum;
              SVN_ERR(svn_checksum_parse_hex(&entry_md5_checksum,
                                             svn_checksum_md5,
                                             entry->checksum, scratch_pool));
              if (text_base_info && text_base_info->revert_base.md5_checksum)
                found_md5_checksum = text_base_info->revert_base.md5_checksum;
              else if (text_base_info
                       && text_base_info->normal_base.md5_checksum)
                found_md5_checksum = text_base_info->normal_base.md5_checksum;
              else
                found_md5_checksum = NULL;
              if (entry_md5_checksum && found_md5_checksum &&
                  !svn_checksum_match(entry_md5_checksum, found_md5_checksum))
                return svn_error_createf(SVN_ERR_WC_CORRUPT, NULL,
                                         _("Bad base MD5 checksum for '%s'; "
                                           "expected: '%s'; found '%s'; "),
                                       svn_dirent_local_style(
                                         svn_dirent_join(root_abspath,
                                                         local_relpath,
                                                         scratch_pool),
                                         scratch_pool),
                                       svn_checksum_to_cstring_display(
                                         entry_md5_checksum, scratch_pool),
                                       svn_checksum_to_cstring_display(
                                         found_md5_checksum, scratch_pool));
              else
                {
                  /* ### Not sure what conditions this should cover. */
                  /* SVN_ERR_ASSERT(entry->deleted || ...); */
                }
            }
        }

      if (this_dir->repos)
        {
          base_node->repos_id = repos_id;

          if (entry->url != NULL)
            {
              base_node->repos_relpath = svn_uri_skip_ancestor(
                                           this_dir->repos, entry->url,
                                           result_pool);
            }
          else
            {
              const char *relpath = svn_uri_skip_ancestor(this_dir->repos,
                                                          this_dir->url,
                                                          scratch_pool);
              if (relpath == NULL || *relpath == '\0')
                base_node->repos_relpath = entry->name;
              else
                base_node->repos_relpath =
                  svn_dirent_join(relpath, entry->name, result_pool);
            }
        }

      /* TODO: These values should always be present, if they are missing
         during an upgrade, set a flag, and then ask the user to talk to the
         server.

         Note: cmt_rev is the distinguishing value. The others may be 0 or
         NULL if the corresponding revprop has been deleted.  */
      base_node->changed_rev = entry->cmt_rev;
      base_node->changed_date = entry->cmt_date;
      base_node->changed_author = entry->cmt_author;

      if (entry->file_external_path)
        base_node->file_external = TRUE;

      /* Switched nodes get an empty iprops cache. */
      if (parent_node
          && is_switched(parent_node->base, base_node, scratch_pool))
        base_node->inherited_props
          = apr_array_make(scratch_pool, 0, sizeof(svn_prop_inherited_item_t*));

      SVN_ERR(insert_node(sdb, base_node, scratch_pool));

      /* We have to insert the lock after the base node, because the node
         must exist to lookup various bits of repos related information for
         the abs path. */
      if (entry->lock_token && create_locks)
        {
          svn_wc__db_lock_t lock;

          lock.token = entry->lock_token;
          lock.owner = entry->lock_owner;
          lock.comment = entry->lock_comment;
          lock.date = entry->lock_creation_date;

          SVN_ERR(svn_wc__db_lock_add(db, tmp_entry_abspath, &lock,
                                      scratch_pool));
        }
    }

  if (below_working_node)
    {
      db_node_t *work
        = parent_node->below_work ? parent_node->below_work : parent_node->work;

      below_working_node->wc_id = wc_id;
      below_working_node->local_relpath = local_relpath;
      below_working_node->op_depth = work->op_depth;
      below_working_node->parent_relpath = parent_relpath;
      below_working_node->presence = svn_wc__db_status_normal;
      below_working_node->kind = entry->kind;
      below_working_node->repos_id = work->repos_id;

      /* This is just guessing. If the node below would have been switched
         or if it was updated to a different version, the guess would
         fail. But we don't have better information pre wc-ng :( */
      if (work->repos_relpath)
        below_working_node->repos_relpath
          = svn_relpath_join(work->repos_relpath, entry->name,
                             result_pool);
      else
        below_working_node->repos_relpath = NULL;
      below_working_node->revision = parent_node->work->revision;

      /* The revert_base checksum isn't available in the entry structure,
         so the caller provides it. */

      /* text_base_info is NULL for files scheduled to be added. */
      below_working_node->checksum = NULL;
      if (text_base_info)
        {
          if (entry->schedule == svn_wc_schedule_delete)
            below_working_node->checksum =
              text_base_info->normal_base.sha1_checksum;
          else
            below_working_node->checksum =
              text_base_info->revert_base.sha1_checksum;
        }
      below_working_node->recorded_size = 0;
      below_working_node->changed_rev = SVN_INVALID_REVNUM;
      below_working_node->changed_date = 0;
      below_working_node->changed_author = NULL;
      below_working_node->depth = svn_depth_infinity;
      below_working_node->recorded_time = 0;
      below_working_node->properties = NULL;

      if (working_node
          && entry->schedule == svn_wc_schedule_delete
          && working_node->repos_relpath)
        {
          /* We are lucky, our guesses above are not necessary. The known
             correct information is in working. But our op_depth design
             expects more information here */
          below_working_node->repos_relpath = working_node->repos_relpath;
          below_working_node->repos_id = working_node->repos_id;
          below_working_node->revision = working_node->revision;

          /* Nice for 'svn status' */
          below_working_node->changed_rev = entry->cmt_rev;
          below_working_node->changed_date = entry->cmt_date;
          below_working_node->changed_author = entry->cmt_author;

          /* And now remove it from WORKING, because in wc-ng code
             should read it from the lower layer */
          working_node->repos_relpath = NULL;
          working_node->repos_id = 0;
          working_node->revision = SVN_INVALID_REVNUM;
        }

      SVN_ERR(insert_node(sdb, below_working_node, scratch_pool));
    }

  /* Insert the working node. */
  if (working_node)
    {
      working_node->wc_id = wc_id;
      working_node->local_relpath = local_relpath;
      working_node->parent_relpath = parent_relpath;
      working_node->changed_rev = SVN_INVALID_REVNUM;
      working_node->recorded_time = entry->text_time;
      working_node->recorded_size = entry->working_size;

      if (entry->depth != svn_depth_exclude)
        working_node->depth = entry->depth;
      else
        {
          working_node->presence = svn_wc__db_status_excluded;
          working_node->depth = svn_depth_infinity;
        }

      if (entry->kind == svn_node_dir)
        working_node->checksum = NULL;
      else
        {
          working_node->checksum = NULL;
          /* text_base_info is NULL for files scheduled to be added. */
          if (text_base_info)
            working_node->checksum = text_base_info->normal_base.sha1_checksum;


          /* If an MD5 checksum is present in the entry, we can verify that
           * it matches the MD5 of the base file we found earlier. */
#ifdef SVN_DEBUG
          if (entry->checksum && text_base_info)
          {
            svn_checksum_t *md5_checksum;
            SVN_ERR(svn_checksum_parse_hex(&md5_checksum, svn_checksum_md5,
                                           entry->checksum, result_pool));
            SVN_ERR_ASSERT(
              md5_checksum && text_base_info->normal_base.md5_checksum);
            SVN_ERR_ASSERT(svn_checksum_match(
              md5_checksum, text_base_info->normal_base.md5_checksum));
          }
#endif
        }

      working_node->kind = entry->kind;
      if (working_node->presence != svn_wc__db_status_excluded)
        {
          /* All subdirs start of incomplete, and stop being incomplete
             when the entries file in the subdir is upgraded. */
          if (entry->kind == svn_node_dir
              && strcmp(entry->name, SVN_WC_ENTRY_THIS_DIR))
            {
              working_node->presence = svn_wc__db_status_incomplete;
              working_node->kind = svn_node_dir;
            }
          else if (entry->schedule == svn_wc_schedule_delete)
            {
              working_node->presence = svn_wc__db_status_base_deleted;
              working_node->kind = entry->kind;
            }
          else
            {
              /* presence == normal  */
              working_node->kind = entry->kind;

              if (entry->incomplete)
                {
                  /* We shouldn't be overwriting another status.  */
                  WRITE_ENTRY_ASSERT(working_node->presence
                                     == svn_wc__db_status_normal);
                  working_node->presence = svn_wc__db_status_incomplete;
                }
            }
        }

      /* These should generally be unset for added and deleted files,
         and contain whatever information we have for copied files. Let's
         just store whatever we have.

         Note: cmt_rev is the distinguishing value. The others may be 0 or
         NULL if the corresponding revprop has been deleted.  */
      if (working_node->presence != svn_wc__db_status_base_deleted)
        {
          working_node->changed_rev = entry->cmt_rev;
          working_node->changed_date = entry->cmt_date;
          working_node->changed_author = entry->cmt_author;
        }

      if (entry->schedule == svn_wc_schedule_delete
          && parent_node->work
          && parent_node->work->presence == svn_wc__db_status_base_deleted)
        {
          working_node->op_depth = parent_node->work->op_depth;
        }
      else if (!entry->copied)
        {
          working_node->op_depth
            = svn_wc__db_op_depth_for_upgrade(local_relpath);
        }

      SVN_ERR(insert_node(sdb, working_node, scratch_pool));
    }

  /* Insert the actual node. */
  if (actual_node)
    {
      actual_node = MAYBE_ALLOC(actual_node, scratch_pool);

      actual_node->wc_id = wc_id;
      actual_node->local_relpath = local_relpath;
      actual_node->parent_relpath = parent_relpath;

      SVN_ERR(insert_actual_node(sdb, db, tmp_entry_abspath,
                                 actual_node, scratch_pool));
    }

  if (entry_node)
    {
      *entry_node = apr_palloc(result_pool, sizeof(**entry_node));
      (*entry_node)->base = base_node;
      (*entry_node)->work = working_node;
      (*entry_node)->below_work = below_working_node;
      (*entry_node)->tree_conflicts = tree_conflicts;
    }

  if (entry->file_external_path)
    {
      /* TODO: Maybe add a file external registration inside EXTERNALS here,
               to allow removing file externals that aren't referenced from
               svn:externals.

         The svn:externals values are processed anyway after everything is
         upgraded */
    }

  return SVN_NO_ERROR;
}

static svn_error_t *
write_actual_only_entries(apr_hash_t *tree_conflicts,
                          svn_sqlite__db_t *sdb,
                          svn_wc__db_t *db,
                          const char *wri_abspath,
                          apr_int64_t wc_id,
                          const char *parent_relpath,
                          apr_pool_t *scratch_pool)
{
  apr_hash_index_t *hi;

  for (hi = apr_hash_first(scratch_pool, tree_conflicts);
       hi;
       hi = apr_hash_next(hi))
    {
      db_actual_node_t *actual_node = NULL;

      actual_node = MAYBE_ALLOC(actual_node, scratch_pool);
      actual_node->wc_id = wc_id;
      actual_node->local_relpath = svn__apr_hash_index_key(hi);
      actual_node->parent_relpath = parent_relpath;
      actual_node->tree_conflict_data = svn__apr_hash_index_val(hi);

      SVN_ERR(insert_actual_node(sdb, db, wri_abspath, actual_node,
                                 scratch_pool));
    }

  return SVN_NO_ERROR;
}

svn_error_t *
svn_wc__write_upgraded_entries(void **dir_baton,
                               void *parent_baton,
                               svn_wc__db_t *db,
                               svn_sqlite__db_t *sdb,
                               apr_int64_t repos_id,
                               apr_int64_t wc_id,
                               const char *dir_abspath,
                               const char *new_root_abspath,
                               apr_hash_t *entries,
                               apr_hash_t *text_bases_info,
                               apr_pool_t *result_pool,
                               apr_pool_t *scratch_pool)
{
  const svn_wc_entry_t *this_dir;
  apr_hash_index_t *hi;
  apr_pool_t *iterpool = svn_pool_create(scratch_pool);
  const char *old_root_abspath, *dir_relpath;
  struct write_baton *parent_node = parent_baton;
  struct write_baton *dir_node;

  /* Get a copy of the "this dir" entry for comparison purposes. */
  this_dir = svn_hash_gets(entries, SVN_WC_ENTRY_THIS_DIR);

  /* If there is no "this dir" entry, something is wrong. */
  if (! this_dir)
    return svn_error_createf(SVN_ERR_ENTRY_NOT_FOUND, NULL,
                             _("No default entry in directory '%s'"),
                             svn_dirent_local_style(dir_abspath,
                                                    iterpool));
  old_root_abspath = svn_dirent_get_longest_ancestor(dir_abspath,
                                                     new_root_abspath,
                                                     scratch_pool);

  SVN_ERR_ASSERT(old_root_abspath[0]);

  dir_relpath = svn_dirent_skip_ancestor(old_root_abspath, dir_abspath);

  /* Write out "this dir" */
  SVN_ERR(write_entry(&dir_node, parent_node, db, sdb,
                      wc_id, repos_id, this_dir, NULL, dir_relpath,
                      svn_dirent_join(new_root_abspath, dir_relpath,
                                      iterpool),
                      old_root_abspath,
                      this_dir, FALSE, result_pool, iterpool));

  for (hi = apr_hash_first(scratch_pool, entries); hi;
       hi = apr_hash_next(hi))
    {
      const char *name = svn__apr_hash_index_key(hi);
      const svn_wc_entry_t *this_entry = svn__apr_hash_index_val(hi);
      const char *child_abspath, *child_relpath;
      svn_wc__text_base_info_t *text_base_info
        = svn_hash_gets(text_bases_info, name);

      svn_pool_clear(iterpool);

      /* Don't rewrite the "this dir" entry! */
      if (strcmp(name, SVN_WC_ENTRY_THIS_DIR) == 0)
        continue;

      /* Write the entry. Pass TRUE for create locks, because we still
         use this function for upgrading old working copies. */
      child_abspath = svn_dirent_join(dir_abspath, name, iterpool);
      child_relpath = svn_dirent_skip_ancestor(old_root_abspath, child_abspath);
      SVN_ERR(write_entry(NULL, dir_node, db, sdb,
                          wc_id, repos_id,
                          this_entry, text_base_info, child_relpath,
                          svn_dirent_join(new_root_abspath, child_relpath,
                                          iterpool),
                          old_root_abspath,
                          this_dir, TRUE, iterpool, iterpool));
    }

  if (dir_node->tree_conflicts)
    SVN_ERR(write_actual_only_entries(dir_node->tree_conflicts, sdb, db,
                                      new_root_abspath, wc_id, dir_relpath,
                                      iterpool));

  *dir_baton = dir_node;
  svn_pool_destroy(iterpool);
  return SVN_NO_ERROR;
}


svn_wc_entry_t *
svn_wc_entry_dup(const svn_wc_entry_t *entry, apr_pool_t *pool)
{
  svn_wc_entry_t *dupentry = apr_palloc(pool, sizeof(*dupentry));

  /* Perform a trivial copy ... */
  *dupentry = *entry;

  /* ...and then re-copy stuff that needs to be duped into our pool. */
  if (entry->name)
    dupentry->name = apr_pstrdup(pool, entry->name);
  if (entry->url)
    dupentry->url = apr_pstrdup(pool, entry->url);
  if (entry->repos)
    dupentry->repos = apr_pstrdup(pool, entry->repos);
  if (entry->uuid)
    dupentry->uuid = apr_pstrdup(pool, entry->uuid);
  if (entry->copyfrom_url)
    dupentry->copyfrom_url = apr_pstrdup(pool, entry->copyfrom_url);
  if (entry->conflict_old)
    dupentry->conflict_old = apr_pstrdup(pool, entry->conflict_old);
  if (entry->conflict_new)
    dupentry->conflict_new = apr_pstrdup(pool, entry->conflict_new);
  if (entry->conflict_wrk)
    dupentry->conflict_wrk = apr_pstrdup(pool, entry->conflict_wrk);
  if (entry->prejfile)
    dupentry->prejfile = apr_pstrdup(pool, entry->prejfile);
  if (entry->checksum)
    dupentry->checksum = apr_pstrdup(pool, entry->checksum);
  if (entry->cmt_author)
    dupentry->cmt_author = apr_pstrdup(pool, entry->cmt_author);
  if (entry->lock_token)
    dupentry->lock_token = apr_pstrdup(pool, entry->lock_token);
  if (entry->lock_owner)
    dupentry->lock_owner = apr_pstrdup(pool, entry->lock_owner);
  if (entry->lock_comment)
    dupentry->lock_comment = apr_pstrdup(pool, entry->lock_comment);
  if (entry->changelist)
    dupentry->changelist = apr_pstrdup(pool, entry->changelist);

  /* NOTE: we do not dup cachable_props or present_props since they
     are deprecated. Use "" to indicate "nothing cachable or cached". */
  dupentry->cachable_props = "";
  dupentry->present_props = "";

  if (entry->tree_conflict_data)
    dupentry->tree_conflict_data = apr_pstrdup(pool,
                                               entry->tree_conflict_data);
  if (entry->file_external_path)
    dupentry->file_external_path = apr_pstrdup(pool,
                                               entry->file_external_path);
  return dupentry;
}


/*** Generic Entry Walker */

/* A recursive entry-walker, helper for svn_wc_walk_entries3().
 *
 * For this directory (DIRPATH, ADM_ACCESS), call the "found_entry" callback
 * in WALK_CALLBACKS, passing WALK_BATON to it. Then, for each versioned
 * entry in this directory, call the "found entry" callback and then recurse
 * (if it is a directory and if DEPTH allows).
 *
 * If SHOW_HIDDEN is true, include entries that are in a 'deleted' or
 * 'absent' state (and not scheduled for re-addition), else skip them.
 *
 * Call CANCEL_FUNC with CANCEL_BATON to allow cancellation.
 */
static svn_error_t *
walker_helper(const char *dirpath,
              svn_wc_adm_access_t *adm_access,
              const svn_wc_entry_callbacks2_t *walk_callbacks,
              void *walk_baton,
              svn_depth_t depth,
              svn_boolean_t show_hidden,
              svn_cancel_func_t cancel_func,
              void *cancel_baton,
              apr_pool_t *pool)
{
  apr_pool_t *subpool = svn_pool_create(pool);
  apr_hash_t *entries;
  apr_hash_index_t *hi;
  svn_wc_entry_t *dot_entry;
  svn_error_t *err;
  svn_wc__db_t *db = svn_wc__adm_get_db(adm_access);

  err = svn_wc__entries_read_internal(&entries, adm_access, show_hidden,
                                      pool);

  if (err)
    SVN_ERR(walk_callbacks->handle_error(dirpath, err, walk_baton, pool));

  /* As promised, always return the '.' entry first. */
  dot_entry = svn_hash_gets(entries, SVN_WC_ENTRY_THIS_DIR);
  if (! dot_entry)
    return walk_callbacks->handle_error
      (dirpath, svn_error_createf(SVN_ERR_ENTRY_NOT_FOUND, NULL,
                                  _("Directory '%s' has no THIS_DIR entry"),
                                  svn_dirent_local_style(dirpath, pool)),
       walk_baton, pool);

  /* Call the "found entry" callback for this directory as a "this dir"
   * entry. Note that if this directory has been reached by recursion, this
   * is the second visit as it will already have been visited once as a
   * child entry of its parent. */

  err = walk_callbacks->found_entry(dirpath, dot_entry, walk_baton, subpool);


  if(err)
    SVN_ERR(walk_callbacks->handle_error(dirpath, err, walk_baton, pool));

  if (depth == svn_depth_empty)
    return SVN_NO_ERROR;

  /* Loop over each of the other entries. */
  for (hi = apr_hash_first(pool, entries); hi; hi = apr_hash_next(hi))
    {
      const char *name = svn__apr_hash_index_key(hi);
      const svn_wc_entry_t *current_entry = svn__apr_hash_index_val(hi);
      const char *entrypath;
      const char *entry_abspath;
      svn_boolean_t hidden;

      svn_pool_clear(subpool);

      /* See if someone wants to cancel this operation. */
      if (cancel_func)
        SVN_ERR(cancel_func(cancel_baton));

      /* Skip the "this dir" entry. */
      if (strcmp(current_entry->name, SVN_WC_ENTRY_THIS_DIR) == 0)
        continue;

      entrypath = svn_dirent_join(dirpath, name, subpool);
      SVN_ERR(svn_wc__entry_is_hidden(&hidden, current_entry));
      SVN_ERR(svn_dirent_get_absolute(&entry_abspath, entrypath, subpool));

      /* Call the "found entry" callback for this entry. (For a directory,
       * this is the first visit: as a child.) */
      if (current_entry->kind == svn_node_file
          || depth >= svn_depth_immediates)
        {
          err = walk_callbacks->found_entry(entrypath, current_entry,
                                            walk_baton, subpool);

          if (err)
            SVN_ERR(walk_callbacks->handle_error(entrypath, err,
                                                 walk_baton, pool));
        }

      /* Recurse into this entry if appropriate. */
      if (current_entry->kind == svn_node_dir
          && !hidden
          && depth >= svn_depth_immediates)
        {
          svn_wc_adm_access_t *entry_access;
          svn_depth_t depth_below_here = depth;

          if (depth == svn_depth_immediates)
            depth_below_here = svn_depth_empty;

          entry_access = svn_wc__adm_retrieve_internal2(db, entry_abspath,
                                                        subpool);

          if (entry_access)
            SVN_ERR(walker_helper(entrypath, entry_access,
                                  walk_callbacks, walk_baton,
                                  depth_below_here, show_hidden,
                                  cancel_func, cancel_baton,
                                  subpool));
        }
    }

  svn_pool_destroy(subpool);
  return SVN_NO_ERROR;
}

svn_error_t *
svn_wc__walker_default_error_handler(const char *path,
                                     svn_error_t *err,
                                     void *walk_baton,
                                     apr_pool_t *pool)
{
  /* Note: don't trace this. We don't want to insert a false "stack frame"
     onto an error generated elsewhere.  */
  return svn_error_trace(err);
}


/* The public API. */
svn_error_t *
svn_wc_walk_entries3(const char *path,
                     svn_wc_adm_access_t *adm_access,
                     const svn_wc_entry_callbacks2_t *walk_callbacks,
                     void *walk_baton,
                     svn_depth_t walk_depth,
                     svn_boolean_t show_hidden,
                     svn_cancel_func_t cancel_func,
                     void *cancel_baton,
                     apr_pool_t *pool)
{
  const char *local_abspath;
  svn_wc__db_t *db = svn_wc__adm_get_db(adm_access);
  svn_error_t *err;
  svn_node_kind_t kind;
  svn_depth_t depth;

  SVN_ERR(svn_dirent_get_absolute(&local_abspath, path, pool));
  err = svn_wc__db_read_info(NULL, &kind, NULL, NULL, NULL, NULL,
                             NULL, NULL, NULL, &depth, NULL, NULL,
                             NULL, NULL, NULL, NULL, NULL, NULL,
                             NULL, NULL, NULL, NULL, NULL, NULL,
                             NULL, NULL, NULL,
                             db, local_abspath,
                             pool, pool);
  if (err)
    {
      if (err->apr_err != SVN_ERR_WC_PATH_NOT_FOUND)
        return svn_error_trace(err);
      /* Remap into SVN_ERR_UNVERSIONED_RESOURCE.  */
      svn_error_clear(err);
      return walk_callbacks->handle_error(
        path, svn_error_createf(SVN_ERR_UNVERSIONED_RESOURCE, NULL,
                                _("'%s' is not under version control"),
                                svn_dirent_local_style(local_abspath, pool)),
        walk_baton, pool);
    }

  if (kind == svn_node_file || depth == svn_depth_exclude)
    {
      const svn_wc_entry_t *entry;

      /* ### we should stop passing out entry structures.
         ###
         ### we should not call handle_error for an error the *callback*
         ###   gave us. let it deal with the problem before returning.  */

      if (!show_hidden)
        {
          svn_boolean_t hidden;
          SVN_ERR(svn_wc__db_node_hidden(&hidden, db, local_abspath, pool));

          if (hidden)
            {
              /* The fool asked to walk a "hidden" node. Report the node as
                 unversioned.

                 ### this is incorrect behavior. see depth_test 36. the walk
                 ### API will be revamped to avoid entry structures. we should
                 ### be able to solve the problem with the new API. (since we
                 ### shouldn't return a hidden entry here)  */
              return walk_callbacks->handle_error(
                               path, svn_error_createf(
                                  SVN_ERR_UNVERSIONED_RESOURCE, NULL,
                                  _("'%s' is not under version control"),
                                  svn_dirent_local_style(local_abspath, pool)),
                               walk_baton, pool);
            }
        }

      SVN_ERR(svn_wc__get_entry(&entry, db, local_abspath, FALSE,
                                svn_node_file, pool, pool));

      err = walk_callbacks->found_entry(path, entry, walk_baton, pool);
      if (err)
        return walk_callbacks->handle_error(path, err, walk_baton, pool);

      return SVN_NO_ERROR;
    }

  if (kind == svn_node_dir)
    return walker_helper(path, adm_access, walk_callbacks, walk_baton,
                         walk_depth, show_hidden, cancel_func, cancel_baton,
                         pool);

  return walk_callbacks->handle_error(
       path, svn_error_createf(SVN_ERR_NODE_UNKNOWN_KIND, NULL,
                               _("'%s' has an unrecognized node kind"),
                               svn_dirent_local_style(local_abspath, pool)),
       walk_baton, pool);
}
