/*
 * branch.c : Element-Based Branching and Move Tracking.
 *
 * ====================================================================
 *    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 <assert.h>

#include "svn_types.h"
#include "svn_error.h"
#include "svn_dirent_uri.h"
#include "svn_hash.h"
#include "svn_iter.h"
#include "svn_pools.h"

#include "private/svn_element.h"
#include "private/svn_branch.h"
#include "private/svn_branch_impl.h"
#include "private/svn_sorts_private.h"

#include "svn_private_config.h"


/* Is EID allocated (no matter whether an element with this id exists)? */
#define EID_IS_ALLOCATED(branch, eid) \
  ((eid) >= (branch)->txn->priv->first_eid \
   && (eid) < (branch)->txn->priv->next_eid)

#define IS_BRANCH_ROOT_EID(branch, eid) \
  ((eid) == (branch)->priv->element_tree->root_eid)

/* Is BRANCH1 the same branch as BRANCH2? Compare by full branch-ids; don't
   require identical branch objects. */
#define BRANCH_IS_SAME_BRANCH(branch1, branch2, scratch_pool) \
  (strcmp(svn_branch__get_id(branch1, scratch_pool), \
          svn_branch__get_id(branch2, scratch_pool)) == 0)

struct svn_branch__txn_priv_t
{
  /* All branches. */
  apr_array_header_t *branches;

  /* The range of element ids assigned. */
  /* EIDs local to the txn are negative, assigned by decrementing FIRST_EID
   * (skipping -1). */
  int first_eid, next_eid;

};

struct svn_branch__state_priv_t
{
  /* EID -> svn_element__content_t mapping. */
  svn_element__tree_t *element_tree;

  /* Merge history for this branch state. */
  svn_branch__history_t *history;

  svn_boolean_t is_flat;

};

static svn_branch__state_t *
branch_state_create(const char *bid,
                    int root_eid,
                    svn_branch__txn_t *txn,
                    apr_pool_t *result_pool);

static svn_error_t *
branch_instantiate_elements(svn_branch__state_t *to_branch,
                            const svn_element__tree_t *elements,
                            apr_pool_t *scratch_pool);

static svn_error_t *
svn_branch__map_add_subtree(svn_branch__state_t *to_branch,
                            int to_eid,
                            svn_branch__eid_t new_parent_eid,
                            const char *new_name,
                            svn_element__tree_t *new_subtree,
                            apr_pool_t *scratch_pool);

/*  */
static apr_pool_t *
branch_state_pool_get(svn_branch__state_t *branch)
{
  return apr_hash_pool_get(branch->priv->element_tree->e_map);
}

/* ### Layering: we didn't want to look at the whole repos in here, but
   copying seems to require it. */
svn_error_t *
svn_branch__repos_get_branch_by_id(svn_branch__state_t **branch_p,
                                  const svn_branch__repos_t *repos,
                                  svn_revnum_t revnum,
                                  const char *branch_id,
                                  apr_pool_t *scratch_pool);

/*  */
static svn_error_t *
branch_in_rev_or_txn(svn_branch__state_t **src_branch,
                     const svn_branch__rev_bid_eid_t *src_el_rev,
                     svn_branch__txn_t *txn,
                     apr_pool_t *result_pool)
{
  if (SVN_IS_VALID_REVNUM(src_el_rev->rev))
    {
      SVN_ERR(svn_branch__repos_get_branch_by_id(src_branch,
                                                 txn->repos,
                                                 src_el_rev->rev,
                                                 src_el_rev->bid,
                                                 result_pool));
    }
  else
    {
      *src_branch
        = svn_branch__txn_get_branch_by_id(txn, src_el_rev->bid, result_pool);
    }

  return SVN_NO_ERROR;
}

/* An #svn_branch__txn_t method. */
static apr_array_header_t *
branch_txn_get_branches(const svn_branch__txn_t *txn,
                        apr_pool_t *result_pool)
{
  return apr_array_copy(result_pool, txn->priv->branches);
}

/* An #svn_branch__txn_t method. */
static svn_error_t *
branch_txn_delete_branch(svn_branch__txn_t *txn,
                         const char *bid,
                         apr_pool_t *scratch_pool)
{
  int i;

  for (i = 0; i < txn->priv->branches->nelts; i++)
    {
      svn_branch__state_t *b = APR_ARRAY_IDX(txn->priv->branches, i, void *);

      if (strcmp(b->bid, bid) == 0)
        {
          SVN_ERR(svn_sort__array_delete2(txn->priv->branches, i, 1));
          break;
        }
    }
  return SVN_NO_ERROR;
}

/* An #svn_branch__txn_t method. */
static svn_error_t *
branch_txn_get_num_new_eids(const svn_branch__txn_t *txn,
                            int *num_new_eids_p,
                            apr_pool_t *scratch_pool)
{
  if (num_new_eids_p)
    *num_new_eids_p = -1 - txn->priv->first_eid;
  return SVN_NO_ERROR;
}

/* An #svn_branch__txn_t method. */
static svn_error_t *
branch_txn_new_eid(svn_branch__txn_t *txn,
                   svn_branch__eid_t *eid_p,
                   apr_pool_t *scratch_pool)
{
  int eid = (txn->priv->first_eid < 0) ? txn->priv->first_eid - 1 : -2;

  txn->priv->first_eid = eid;
  if (eid_p)
    *eid_p = eid;
  return SVN_NO_ERROR;
}

/* An #svn_branch__txn_t method. */
static svn_error_t *
branch_txn_open_branch(svn_branch__txn_t *txn,
                       svn_branch__state_t **new_branch_p,
                       const char *branch_id,
                       int root_eid,
                       svn_branch__rev_bid_eid_t *tree_ref,
                       apr_pool_t *result_pool,
                       apr_pool_t *scratch_pool)
{
  svn_branch__state_t *new_branch;

  /* if the branch already exists, just return it, else create it */
  new_branch
    = svn_branch__txn_get_branch_by_id(txn, branch_id, scratch_pool);
  if (new_branch)
    {
      SVN_ERR_ASSERT(root_eid == svn_branch__root_eid(new_branch));
    }
  else
    {
      SVN_ERR_ASSERT_NO_RETURN(root_eid != -1);

      new_branch = branch_state_create(branch_id, root_eid, txn,
                                       txn->priv->branches->pool);
      APR_ARRAY_PUSH(txn->priv->branches, void *) = new_branch;
    }

  if (tree_ref)
    {
      svn_branch__state_t *from_branch;
      svn_element__tree_t *tree;

      SVN_ERR(branch_in_rev_or_txn(&from_branch, tree_ref, txn, scratch_pool));
      /* Source branch must exist */
      if (! from_branch)
        {
          return svn_error_createf(SVN_BRANCH__ERR, NULL,
                                   _("Cannot branch from r%ld %s e%d: "
                                     "branch does not exist"),
                                   tree_ref->rev, tree_ref->bid, tree_ref->eid);
        }

      SVN_ERR_ASSERT(from_branch->priv->is_flat);

      SVN_ERR(svn_branch__state_get_elements(from_branch, &tree,
                                             scratch_pool));
      tree = svn_element__tree_get_subtree_at_eid(tree, tree_ref->eid,
                                                  scratch_pool);
      /* Source element must exist */
      if (! tree)
        {
          return svn_error_createf(SVN_BRANCH__ERR, NULL,
                                   _("Cannot branch from r%ld %s e%d: "
                                     "element does not exist"),
                                   tree_ref->rev, tree_ref->bid, tree_ref->eid);
        }

      /* Populate the tree from the 'from' source */
      SVN_ERR(branch_instantiate_elements(new_branch, tree, scratch_pool));
    }

  if (new_branch_p)
    *new_branch_p = new_branch;
  return SVN_NO_ERROR;
}

/* An #svn_branch__txn_t method. */
static svn_error_t *
branch_txn_sequence_point(svn_branch__txn_t *txn,
                          apr_pool_t *scratch_pool)
{
  int i;

  /* purge elements in each branch */
  for (i = 0; i < txn->priv->branches->nelts; i++)
    {
      svn_branch__state_t *b
        = APR_ARRAY_IDX(txn->priv->branches, i, void *);

      SVN_ERR(svn_branch__state_purge(b, scratch_pool));
    }

  return SVN_NO_ERROR;
}

/* An #svn_branch__txn_t method. */
static svn_error_t *
branch_txn_complete(svn_branch__txn_t *txn,
                    apr_pool_t *scratch_pool)
{
  return SVN_NO_ERROR;
}

/* An #svn_branch__txn_t method. */
static svn_error_t *
branch_txn_abort(svn_branch__txn_t *txn,
                 apr_pool_t *scratch_pool)
{
  return SVN_NO_ERROR;
}

/*
 * ========================================================================
 * Branch Txn Object
 * ========================================================================
 */

apr_array_header_t *
svn_branch__txn_get_branches(const svn_branch__txn_t *txn,
                             apr_pool_t *result_pool)
{
  apr_array_header_t *branches
    = txn->vtable->get_branches(txn,
                                result_pool);
  return branches;
}

svn_error_t *
svn_branch__txn_delete_branch(svn_branch__txn_t *txn,
                              const char *bid,
                              apr_pool_t *scratch_pool)
{
  SVN_ERR(txn->vtable->delete_branch(txn,
                                    bid,
                                    scratch_pool));
  return SVN_NO_ERROR;
}

svn_error_t *
svn_branch__txn_get_num_new_eids(const svn_branch__txn_t *txn,
                                 int *num_new_eids_p,
                                 apr_pool_t *scratch_pool)
{
  SVN_ERR(txn->vtable->get_num_new_eids(txn,
                                        num_new_eids_p,
                                        scratch_pool));
  return SVN_NO_ERROR;
}

svn_error_t *
svn_branch__txn_new_eid(svn_branch__txn_t *txn,
                        int *new_eid_p,
                        apr_pool_t *scratch_pool)
{
  SVN_ERR(txn->vtable->new_eid(txn,
                               new_eid_p,
                               scratch_pool));
  return SVN_NO_ERROR;
}

svn_error_t *
svn_branch__txn_open_branch(svn_branch__txn_t *txn,
                            svn_branch__state_t **new_branch_p,
                            const char *branch_id,
                            int root_eid,
                            svn_branch__rev_bid_eid_t *tree_ref,
                            apr_pool_t *result_pool,
                            apr_pool_t *scratch_pool)
{
  SVN_ERR(txn->vtable->open_branch(txn,
                                   new_branch_p,
                                   branch_id,
                                   root_eid, tree_ref, result_pool,
                                   scratch_pool));
  return SVN_NO_ERROR;
}

svn_error_t *
svn_branch__txn_finalize_eids(svn_branch__txn_t *txn,
                              apr_pool_t *scratch_pool)
{
  SVN_ERR(txn->vtable->finalize_eids(txn,
                                     scratch_pool));
  return SVN_NO_ERROR;
}

svn_error_t *
svn_branch__txn_serialize(svn_branch__txn_t *txn,
                          svn_stream_t *stream,
                          apr_pool_t *scratch_pool)
{
  SVN_ERR(txn->vtable->serialize(txn,
                                 stream,
                                 scratch_pool));
  return SVN_NO_ERROR;
}

svn_error_t *
svn_branch__txn_sequence_point(svn_branch__txn_t *txn,
                               apr_pool_t *scratch_pool)
{
  SVN_ERR(txn->vtable->sequence_point(txn,
                                      scratch_pool));
  return SVN_NO_ERROR;
}

svn_error_t *
svn_branch__txn_complete(svn_branch__txn_t *txn,
                         apr_pool_t *scratch_pool)
{
  SVN_ERR(txn->vtable->complete(txn,
                                scratch_pool));
  return SVN_NO_ERROR;
}

svn_error_t *
svn_branch__txn_abort(svn_branch__txn_t *txn,
                      apr_pool_t *scratch_pool)
{
  SVN_ERR(txn->vtable->abort(txn,
                             scratch_pool));
  return SVN_NO_ERROR;
}

svn_branch__txn_t *
svn_branch__txn_create(const svn_branch__txn_vtable_t *vtable,
                       svn_cancel_func_t cancel_func,
                       void *cancel_baton,
                       apr_pool_t *result_pool)
{
  svn_branch__txn_t *txn = apr_pcalloc(result_pool, sizeof(*txn));

  txn->vtable = apr_pmemdup(result_pool, vtable, sizeof(*vtable));

  txn->vtable->vpriv.cancel_func = cancel_func;
  txn->vtable->vpriv.cancel_baton = cancel_baton;

#ifdef ENABLE_ORDERING_CHECK
  txn->vtable->vpriv.within_callback = FALSE;
  txn->vtable->vpriv.finished = FALSE;
  txn->vtable->vpriv.state_pool = result_pool;
#endif

  return txn;
}

/*
 * ========================================================================
 */

/*  */
static const char *
branch_finalize_bid(const char *bid,
                    int mapping_offset,
                    apr_pool_t *result_pool)
{
  const char *outer_bid;
  int outer_eid;

  svn_branch__id_unnest(&outer_bid, &outer_eid, bid, result_pool);

  if (outer_bid)
    {
      outer_bid = branch_finalize_bid(outer_bid, mapping_offset, result_pool);
    }

  if (outer_eid < -1)
    {
      outer_eid = mapping_offset - outer_eid;
    }

  return svn_branch__id_nest(outer_bid, outer_eid, result_pool);
}

/* Change txn-local EIDs (negative integers) in BRANCH to revision EIDs, by
 * assigning a new revision-EID (positive integer) for each one.
 */
static svn_error_t *
branch_finalize_eids(svn_branch__state_t *branch,
                     int mapping_offset,
                     apr_pool_t *scratch_pool)
{
  apr_hash_index_t *hi;

  branch->bid = branch_finalize_bid(branch->bid, mapping_offset,
                                    branch_state_pool_get(branch));
  if (branch->priv->element_tree->root_eid < -1)
    {
      branch->priv->element_tree->root_eid
        = mapping_offset - branch->priv->element_tree->root_eid;
    }

  for (hi = apr_hash_first(scratch_pool, branch->priv->element_tree->e_map);
       hi; hi = apr_hash_next(hi))
    {
      int old_eid = svn_eid__hash_this_key(hi);
      svn_element__content_t *element = apr_hash_this_val(hi);

      if (old_eid < -1)
        {
          int new_eid = mapping_offset - old_eid;

          svn_element__tree_set(branch->priv->element_tree, old_eid, NULL);
          svn_element__tree_set(branch->priv->element_tree, new_eid, element);
        }
      if (element->parent_eid < -1)
        {
          element->parent_eid = mapping_offset - element->parent_eid;
        }
    }
  return SVN_NO_ERROR;
}

/* An #svn_branch__txn_t method. */
static svn_error_t *
branch_txn_finalize_eids(svn_branch__txn_t *txn,
                         apr_pool_t *scratch_pool)
{
  int n_txn_eids = (-1) - txn->priv->first_eid;
  int mapping_offset;
  apr_array_header_t *branches = branch_txn_get_branches(txn, scratch_pool);
  int i;

  if (txn->priv->first_eid == 0)
    return SVN_NO_ERROR;

  /* mapping from txn-local (negative) EID to committed (positive) EID is:
       txn_local_eid == -2  =>  committed_eid := (txn.next_eid + 0)
       txn_local_eid == -3  =>  committed_eid := (txn.next_eid + 1) ... */
  mapping_offset = txn->priv->next_eid - 2;

  for (i = 0; i < branches->nelts; i++)
    {
      svn_branch__state_t *b = APR_ARRAY_IDX(branches, i, void *);

      SVN_ERR(branch_finalize_eids(b, mapping_offset, scratch_pool));
    }

  txn->priv->next_eid += n_txn_eids;
  txn->priv->first_eid = 0;
  return SVN_NO_ERROR;
}

/*
 * ========================================================================
 */

static svn_error_t *
branch_txn_serialize(svn_branch__txn_t *txn,
                     svn_stream_t *stream,
                     apr_pool_t *scratch_pool)
{
  apr_array_header_t *branches = branch_txn_get_branches(txn, scratch_pool);
  int i;

  SVN_ERR(svn_stream_printf(stream, scratch_pool,
                            "r%ld: eids %d %d "
                            "branches %d\n",
                            txn->rev,
                            txn->priv->first_eid, txn->priv->next_eid,
                            branches->nelts));

  for (i = 0; i < branches->nelts; i++)
    {
      svn_branch__state_t *branch = APR_ARRAY_IDX(branches, i, void *);

      SVN_ERR(svn_branch__state_serialize(stream, branch, scratch_pool));
    }
  return SVN_NO_ERROR;
}

/*
 * ========================================================================
 */

svn_branch__state_t *
svn_branch__txn_get_branch_by_id(const svn_branch__txn_t *txn,
                                 const char *branch_id,
                                 apr_pool_t *scratch_pool)
{
  apr_array_header_t *branches = svn_branch__txn_get_branches(txn, scratch_pool);
  int i;
  svn_branch__state_t *branch = NULL;

  for (i = 0; i < branches->nelts; i++)
    {
      svn_branch__state_t *b = APR_ARRAY_IDX(branches, i, void *);

      if (strcmp(svn_branch__get_id(b, scratch_pool), branch_id) == 0)
        {
          branch = b;
          break;
        }
    }
  return branch;
}

/*
 * ========================================================================
 */

/* Create a new branch txn object.
 *
 * It will have no branches.
 */
static svn_branch__txn_t *
branch_txn_create(svn_branch__repos_t *repos,
                  svn_revnum_t rev,
                  svn_revnum_t base_rev,
                  apr_pool_t *result_pool)
{
  static const svn_branch__txn_vtable_t vtable = {
    {0},
    branch_txn_get_branches,
    branch_txn_delete_branch,
    branch_txn_get_num_new_eids,
    branch_txn_new_eid,
    branch_txn_open_branch,
    branch_txn_finalize_eids,
    branch_txn_serialize,
    branch_txn_sequence_point,
    branch_txn_complete,
    branch_txn_abort,
  };
  svn_branch__txn_t *txn
    = svn_branch__txn_create(&vtable, NULL, NULL, result_pool);

  txn->priv = apr_pcalloc(result_pool, sizeof(*txn->priv));
  txn->repos = repos;
  txn->rev = rev;
  txn->base_rev = base_rev;
  txn->priv->branches = apr_array_make(result_pool, 0, sizeof(void *));
  return txn;
}

/*
 * ========================================================================
 */

static void
branch_validate_element(const svn_branch__state_t *branch,
                        int eid,
                        const svn_element__content_t *element);

/* Assert BRANCH satisfies all its invariants.
 */
static void
assert_branch_state_invariants(const svn_branch__state_t *branch,
                               apr_pool_t *scratch_pool)
{
  apr_hash_index_t *hi;

  assert(branch->bid);
  assert(branch->txn);
  assert(branch->priv->element_tree);
  assert(branch->priv->element_tree->e_map);

  /* Validate elements in the map */
  for (hi = apr_hash_first(scratch_pool, branch->priv->element_tree->e_map);
       hi; hi = apr_hash_next(hi))
    {
      branch_validate_element(branch, svn_eid__hash_this_key(hi),
                              apr_hash_this_val(hi));
    }
}

/* An #svn_branch__state_t method. */
static svn_error_t *
branch_state_copy_one(svn_branch__state_t *branch,
                      const svn_branch__rev_bid_eid_t *src_el_rev,
                      svn_branch__eid_t eid,
                      svn_branch__eid_t new_parent_eid,
                      const char *new_name,
                      const svn_element__payload_t *new_payload,
                      apr_pool_t *scratch_pool)
{
  /* New payload shall be the same as the source if NEW_PAYLOAD is null. */
  /* ### if (! new_payload)
    {
      new_payload = branch_map_get(branch, eid)->payload;
    }
   */

  return SVN_NO_ERROR;
}

/* Copy a subtree.
 *
 * Adjust TO_BRANCH and its subbranches (recursively), to reflect a copy
 * of a subtree from FROM_EL_REV to TO_PARENT_EID:TO_NAME.
 *
 * FROM_EL_REV must be an existing element. (It may be a branch root.)
 *
 * ### TODO:
 * If FROM_EL_REV is the root of a subbranch and/or contains nested
 * subbranches, also copy them ...
 * ### What shall we do with a subbranch? Make plain copies of its raw
 *     elements; make a subbranch by branching the source subbranch?
 *
 * TO_PARENT_EID must be a directory element in TO_BRANCH, and TO_NAME a
 * non-existing path in it.
 */
static svn_error_t *
copy_subtree(const svn_branch__el_rev_id_t *from_el_rev,
             svn_branch__state_t *to_branch,
             svn_branch__eid_t to_parent_eid,
             const char *to_name,
             apr_pool_t *scratch_pool)
{
  svn_element__tree_t *new_subtree;

  SVN_ERR_ASSERT(from_el_rev->branch->priv->is_flat);

  SVN_ERR(svn_branch__state_get_elements(from_el_rev->branch, &new_subtree,
                                         scratch_pool));
  new_subtree = svn_element__tree_get_subtree_at_eid(new_subtree,
                                                     from_el_rev->eid,
                                                     scratch_pool);

  /* copy the subtree, assigning new EIDs */
  SVN_ERR(svn_branch__map_add_subtree(to_branch, -1 /*to_eid*/,
                                      to_parent_eid, to_name,
                                      new_subtree,
                                      scratch_pool));

  return SVN_NO_ERROR;
}

/* An #svn_branch__state_t method. */
static svn_error_t *
branch_state_copy_tree(svn_branch__state_t *to_branch,
                       const svn_branch__rev_bid_eid_t *src_el_rev,
                       svn_branch__eid_t new_parent_eid,
                       const char *new_name,
                       apr_pool_t *scratch_pool)
{
  svn_branch__txn_t *txn = to_branch->txn;
  svn_branch__state_t *src_branch;
  svn_branch__el_rev_id_t *from_el_rev;

  SVN_ERR(branch_in_rev_or_txn(&src_branch, src_el_rev, txn, scratch_pool));
  from_el_rev = svn_branch__el_rev_id_create(src_branch, src_el_rev->eid,
                                             src_el_rev->rev, scratch_pool);
  SVN_ERR(copy_subtree(from_el_rev,
                       to_branch, new_parent_eid, new_name,
                       scratch_pool));

  return SVN_NO_ERROR;
}

const char *
svn_branch__get_id(const svn_branch__state_t *branch,
                   apr_pool_t *result_pool)
{
  return branch->bid;
}

int
svn_branch__root_eid(const svn_branch__state_t *branch)
{
  svn_element__tree_t *elements;

  svn_error_clear(svn_branch__state_get_elements(branch, &elements,
                                                 NULL/*scratch_pool*/));
  return elements->root_eid;
}

svn_branch__el_rev_id_t *
svn_branch__el_rev_id_create(svn_branch__state_t *branch,
                             int eid,
                             svn_revnum_t rev,
                             apr_pool_t *result_pool)
{
  svn_branch__el_rev_id_t *id = apr_palloc(result_pool, sizeof(*id));

  id->branch = branch;
  id->eid = eid;
  id->rev = rev;
  return id;
}

svn_branch__el_rev_id_t *
svn_branch__el_rev_id_dup(const svn_branch__el_rev_id_t *old_id,
                          apr_pool_t *result_pool)
{
  if (! old_id)
    return NULL;

  return svn_branch__el_rev_id_create(old_id->branch,
                                      old_id->eid,
                                      old_id->rev,
                                      result_pool);
}

svn_branch__rev_bid_eid_t *
svn_branch__rev_bid_eid_create(svn_revnum_t rev,
                               const char *branch_id,
                               int eid,
                               apr_pool_t *result_pool)
{
  svn_branch__rev_bid_eid_t *id = apr_palloc(result_pool, sizeof(*id));

  id->bid = apr_pstrdup(result_pool, branch_id);
  id->eid = eid;
  id->rev = rev;
  return id;
}

svn_branch__rev_bid_eid_t *
svn_branch__rev_bid_eid_dup(const svn_branch__rev_bid_eid_t *old_id,
                            apr_pool_t *result_pool)
{
  svn_branch__rev_bid_eid_t *id;

  if (! old_id)
    return NULL;

  id = apr_pmemdup(result_pool, old_id, sizeof(*id));
  id->bid = apr_pstrdup(result_pool, old_id->bid);
  return id;
}

svn_branch__rev_bid_t *
svn_branch__rev_bid_create(svn_revnum_t rev,
                           const char *branch_id,
                           apr_pool_t *result_pool)
{
  svn_branch__rev_bid_t *id = apr_palloc(result_pool, sizeof(*id));

  id->bid = apr_pstrdup(result_pool, branch_id);
  id->rev = rev;
  return id;
}

svn_branch__rev_bid_t *
svn_branch__rev_bid_dup(const svn_branch__rev_bid_t *old_id,
                        apr_pool_t *result_pool)
{
  svn_branch__rev_bid_t *id;

  if (! old_id)
    return NULL;

  id = apr_pmemdup(result_pool, old_id, sizeof(*id));
  id->bid = apr_pstrdup(result_pool, old_id->bid);
  return id;
}

svn_boolean_t
svn_branch__rev_bid_equal(const svn_branch__rev_bid_t *id1,
                          const svn_branch__rev_bid_t *id2)
{
  return (id1->rev == id2->rev
          && strcmp(id1->bid, id2->bid) == 0);
}

svn_branch__history_t *
svn_branch__history_create_empty(apr_pool_t *result_pool)
{
  svn_branch__history_t *history
    = svn_branch__history_create(NULL, result_pool);

  return history;
}

svn_branch__history_t *
svn_branch__history_create(apr_hash_t *parents,
                           apr_pool_t *result_pool)
{
  svn_branch__history_t *history
    = apr_pcalloc(result_pool, sizeof(*history));

  history->parents = apr_hash_make(result_pool);
  if (parents)
    {
      apr_hash_index_t *hi;

      for (hi = apr_hash_first(result_pool, parents);
           hi; hi = apr_hash_next(hi))
        {
          const char *bid = apr_hash_this_key(hi);
          svn_branch__rev_bid_t *val = apr_hash_this_val(hi);

          svn_hash_sets(history->parents,
                        apr_pstrdup(result_pool, bid),
                        svn_branch__rev_bid_dup(val, result_pool));
        }
    }
  return history;
}

svn_branch__history_t *
svn_branch__history_dup(const svn_branch__history_t *old,
                        apr_pool_t *result_pool)
{
  svn_branch__history_t *history = NULL;

  if (old)
    {
      history
        = svn_branch__history_create(old->parents, result_pool);
    }
  return history;
}


/*
 * ========================================================================
 * Branch mappings
 * ========================================================================
 */

/* Validate that ELEMENT is suitable for a mapping of BRANCH:EID.
 * ELEMENT->payload may be null.
 */
static void
branch_validate_element(const svn_branch__state_t *branch,
                        int eid,
                        const svn_element__content_t *element)
{
  SVN_ERR_ASSERT_NO_RETURN(element);

  /* Parent EID must be valid and different from this element's EID, or -1
     iff this is the branch root element. */
  SVN_ERR_ASSERT_NO_RETURN(
    IS_BRANCH_ROOT_EID(branch, eid)
    ? (element->parent_eid == -1)
    : (element->parent_eid != eid
       && EID_IS_ALLOCATED(branch, element->parent_eid)));

  /* Element name must be given, and empty iff EID is the branch root. */
  SVN_ERR_ASSERT_NO_RETURN(
    element->name
    && IS_BRANCH_ROOT_EID(branch, eid) == (*element->name == '\0'));

  SVN_ERR_ASSERT_NO_RETURN(svn_element__payload_invariants(element->payload));
  if (element->payload->is_subbranch_root)
    {
      /* a subbranch root element must not be the branch root element */
      SVN_ERR_ASSERT_NO_RETURN(! IS_BRANCH_ROOT_EID(branch, eid));
    }
}

static svn_error_t *
branch_state_get_elements(const svn_branch__state_t *branch,
                          svn_element__tree_t **element_tree_p,
                          apr_pool_t *result_pool)
{
  *element_tree_p = branch->priv->element_tree;
  return SVN_NO_ERROR;
}

static svn_element__content_t *
branch_get_element(const svn_branch__state_t *branch,
                   int eid)
{
  svn_element__content_t *element;

  element = svn_element__tree_get(branch->priv->element_tree, eid);

  if (element)
    branch_validate_element(branch, eid, element);
  return element;
}

static svn_error_t *
branch_state_get_element(const svn_branch__state_t *branch,
                         svn_element__content_t **element_p,
                         int eid,
                         apr_pool_t *result_pool)
{
  *element_p = branch_get_element(branch, eid);
  return SVN_NO_ERROR;
}

/* In BRANCH, set element EID to ELEMENT.
 *
 * If ELEMENT is null, delete element EID.
 *
 * Assume ELEMENT is already allocated with sufficient lifetime.
 */
static void
branch_map_set(svn_branch__state_t *branch,
               int eid,
               const svn_element__content_t *element)
{
  apr_pool_t *map_pool = apr_hash_pool_get(branch->priv->element_tree->e_map);

  SVN_ERR_ASSERT_NO_RETURN(EID_IS_ALLOCATED(branch, eid));
  if (element)
    branch_validate_element(branch, eid, element);

  svn_element__tree_set(branch->priv->element_tree, eid, element);
  branch->priv->is_flat = FALSE;
  assert_branch_state_invariants(branch, map_pool);
}

/* An #svn_branch__state_t method. */
static svn_error_t *
branch_state_set_element(svn_branch__state_t *branch,
                         svn_branch__eid_t eid,
                         const svn_element__content_t *element,
                         apr_pool_t *scratch_pool)
{
  apr_pool_t *map_pool = apr_hash_pool_get(branch->priv->element_tree->e_map);

  /* EID must be a valid element id */
  SVN_ERR_ASSERT(EID_IS_ALLOCATED(branch, eid));

  if (element)
    {
      element = svn_element__content_dup(element, map_pool);

      /* NEW_PAYLOAD must be specified, either in full or by reference */
      SVN_ERR_ASSERT(element->payload);

      if ((element->parent_eid == -1) != IS_BRANCH_ROOT_EID(branch, eid)
          || (*element->name == '\0') != IS_BRANCH_ROOT_EID(branch, eid))
        {
          return svn_error_createf(SVN_BRANCH__ERR, NULL,
                                   _("Cannot set e%d to (parent=e%d, name='%s'): "
                                     "branch root is e%d"),
                                   eid, element->parent_eid, element->name,
                                   branch->priv->element_tree->root_eid);
        }
    }

  /* Insert the new version */
  branch_map_set(branch, eid, element);
  return SVN_NO_ERROR;
}

/* An #svn_branch__state_t method. */
static svn_error_t *
branch_state_purge(svn_branch__state_t *branch,
                   apr_pool_t *scratch_pool)
{
  svn_element__tree_purge_orphans(branch->priv->element_tree->e_map,
                                  branch->priv->element_tree->root_eid,
                                  scratch_pool);
  branch->priv->is_flat = TRUE;
  return SVN_NO_ERROR;
}

/* An #svn_branch__state_t method. */
static svn_error_t *
branch_state_get_history(svn_branch__state_t *branch,
                         svn_branch__history_t **history_p,
                         apr_pool_t *result_pool)
{
  if (history_p)
    {
      *history_p
        = svn_branch__history_dup(branch->priv->history, result_pool);
    }
  return SVN_NO_ERROR;
}

/* An #svn_branch__state_t method. */
static svn_error_t *
branch_state_set_history(svn_branch__state_t *branch,
                         const svn_branch__history_t *history,
                         apr_pool_t *scratch_pool)
{
  apr_pool_t *branch_pool = branch_state_pool_get(branch);

  branch->priv->history
    = svn_branch__history_dup(history, branch_pool);
  return SVN_NO_ERROR;
}

const char *
svn_branch__get_path_by_eid(const svn_branch__state_t *branch,
                            int eid,
                            apr_pool_t *result_pool)
{
  svn_element__tree_t *elements;

  SVN_ERR_ASSERT_NO_RETURN(EID_IS_ALLOCATED(branch, eid));
  /*SVN_ERR_ASSERT_NO_RETURN(branch->priv->is_flat);*/

  svn_error_clear(svn_branch__state_get_elements(branch, &elements, result_pool));
  return svn_element__tree_get_path_by_eid(elements, eid, result_pool);
}

int
svn_branch__get_eid_by_path(const svn_branch__state_t *branch,
                            const char *path,
                            apr_pool_t *scratch_pool)
{
  svn_element__tree_t *elements;
  apr_hash_index_t *hi;

  /*SVN_ERR_ASSERT_NO_RETURN(branch->priv->is_flat);*/

  /* ### This is a crude, linear search */
  svn_error_clear(svn_branch__state_get_elements(branch, &elements, scratch_pool));
  for (hi = apr_hash_first(scratch_pool, elements->e_map);
       hi; hi = apr_hash_next(hi))
    {
      int eid = svn_eid__hash_this_key(hi);
      const char *this_path = svn_element__tree_get_path_by_eid(elements, eid,
                                                                scratch_pool);

      if (! this_path)
        {
          /* Mapping is not complete; this element is in effect not present. */
          continue;
        }
      if (strcmp(path, this_path) == 0)
        {
          return eid;
        }
    }

  return -1;
}

/* Create a copy of NEW_SUBTREE in TO_BRANCH.
 *
 * For each non-root element in NEW_SUBTREE, create a new element with
 * a new EID, no matter what EID is used to represent it in NEW_SUBTREE.
 *
 * For the new subtree root element, if TO_EID is -1, generate a new EID,
 * otherwise alter (if it exists) or instantiate the element TO_EID.
 *
 * Set the new subtree root element's parent to NEW_PARENT_EID and name to
 * NEW_NAME.
 */
static svn_error_t *
svn_branch__map_add_subtree(svn_branch__state_t *to_branch,
                            int to_eid,
                            svn_branch__eid_t new_parent_eid,
                            const char *new_name,
                            svn_element__tree_t *new_subtree,
                            apr_pool_t *scratch_pool)
{
  apr_hash_index_t *hi;
  svn_element__content_t *new_root_content;

  /* Get a new EID for the root element, if not given. */
  if (to_eid == -1)
    {
      SVN_ERR(svn_branch__txn_new_eid(to_branch->txn, &to_eid,
                                      scratch_pool));
    }

  /* Create the new subtree root element */
  new_root_content = svn_element__tree_get(new_subtree, new_subtree->root_eid);
  new_root_content = svn_element__content_create(new_parent_eid, new_name,
                                                 new_root_content->payload,
                                                 scratch_pool);
  SVN_ERR(branch_state_set_element(to_branch, to_eid, new_root_content,
                                   scratch_pool));

  /* Process its immediate children */
  for (hi = apr_hash_first(scratch_pool, new_subtree->e_map);
       hi; hi = apr_hash_next(hi))
    {
      int this_from_eid = svn_eid__hash_this_key(hi);
      svn_element__content_t *from_element = apr_hash_this_val(hi);

      if (from_element->parent_eid == new_subtree->root_eid)
        {
          svn_element__tree_t *this_subtree;

          /* Recurse. (We don't try to check whether it's a directory node,
             as we might not have the node kind in the map.) */
          this_subtree
            = svn_element__tree_create(new_subtree->e_map, this_from_eid,
                                       scratch_pool);
          SVN_ERR(svn_branch__map_add_subtree(to_branch, -1 /*to_eid*/,
                                              to_eid, from_element->name,
                                              this_subtree, scratch_pool));
        }
    }

  return SVN_NO_ERROR;
}

/* Instantiate elements in a branch.
 *
 * In TO_BRANCH, instantiate (or alter, if existing) each element of
 * ELEMENTS, each with its given tree structure (parent, name) and payload.
 */
static svn_error_t *
branch_instantiate_elements(svn_branch__state_t *to_branch,
                            const svn_element__tree_t *elements,
                            apr_pool_t *scratch_pool)
{
  apr_hash_index_t *hi;

  for (hi = apr_hash_first(scratch_pool, elements->e_map);
       hi; hi = apr_hash_next(hi))
    {
      int this_eid = svn_eid__hash_this_key(hi);
      svn_element__content_t *this_element = apr_hash_this_val(hi);

      branch_map_set(to_branch, this_eid,
                     svn_element__content_dup(
                       this_element,
                       apr_hash_pool_get(to_branch->priv->element_tree->e_map)));
    }

  return SVN_NO_ERROR;
}

/*
 * ========================================================================
 * Branch State Object
 * ========================================================================
 */

svn_error_t *
svn_branch__state_get_elements(const svn_branch__state_t *branch,
                               svn_element__tree_t **element_tree_p,
                               apr_pool_t *result_pool)
{
  SVN_ERR(branch->vtable->get_elements(branch,
                                       element_tree_p,
                                       result_pool));
  return SVN_NO_ERROR;
}

svn_error_t *
svn_branch__state_get_element(const svn_branch__state_t *branch,
                              svn_element__content_t **element_p,
                              int eid,
                              apr_pool_t *result_pool)
{
  SVN_ERR(branch->vtable->get_element(branch,
                                      element_p, eid, result_pool));
  return SVN_NO_ERROR;
}

svn_error_t *
svn_branch__state_set_element(svn_branch__state_t *branch,
                              int eid,
                              const svn_element__content_t *element,
                              apr_pool_t *scratch_pool)
{
  SVN_ERR(branch->vtable->set_element(branch,
                                      eid, element,
                                      scratch_pool));
  return SVN_NO_ERROR;
}

svn_error_t *
svn_branch__state_alter_one(svn_branch__state_t *branch,
                            svn_branch__eid_t eid,
                            svn_branch__eid_t new_parent_eid,
                            const char *new_name,
                            const svn_element__payload_t *new_payload,
                            apr_pool_t *scratch_pool)
{
  svn_element__content_t *element
    = svn_element__content_create(new_parent_eid, new_name, new_payload,
                                  scratch_pool);

  SVN_ERR(svn_branch__state_set_element(branch, eid, element, scratch_pool));
  return SVN_NO_ERROR;
}

svn_error_t *
svn_branch__state_copy_tree(svn_branch__state_t *branch,
                            const svn_branch__rev_bid_eid_t *src_el_rev,
                            svn_branch__eid_t new_parent_eid,
                            const char *new_name,
                            apr_pool_t *scratch_pool)
{
  SVN_ERR(branch->vtable->copy_tree(branch,
                                    src_el_rev, new_parent_eid, new_name,
                                    scratch_pool));
  return SVN_NO_ERROR;
}

svn_error_t *
svn_branch__state_delete_one(svn_branch__state_t *branch,
                             svn_branch__eid_t eid,
                             apr_pool_t *scratch_pool)
{
  SVN_ERR(svn_branch__state_set_element(branch, eid, NULL, scratch_pool));
  return SVN_NO_ERROR;
}

svn_error_t *
svn_branch__state_purge(svn_branch__state_t *branch,
                        apr_pool_t *scratch_pool)
{
  SVN_ERR(branch->vtable->purge(branch,
                                scratch_pool));
  return SVN_NO_ERROR;
}

svn_error_t *
svn_branch__state_get_history(svn_branch__state_t *branch,
                              svn_branch__history_t **history_p,
                              apr_pool_t *result_pool)
{
  SVN_ERR(branch->vtable->get_history(branch,
                                      history_p,
                                      result_pool));
  SVN_ERR_ASSERT(*history_p);
  return SVN_NO_ERROR;
}

svn_error_t *
svn_branch__state_set_history(svn_branch__state_t *branch,
                              const svn_branch__history_t *history,
                              apr_pool_t *scratch_pool)
{
  SVN_ERR_ASSERT(history);
  SVN_ERR(branch->vtable->set_history(branch,
                                      history,
                                      scratch_pool));
  return SVN_NO_ERROR;
}

svn_branch__state_t *
svn_branch__state_create(const svn_branch__state_vtable_t *vtable,
                         svn_cancel_func_t cancel_func,
                         void *cancel_baton,
                         apr_pool_t *result_pool)
{
  svn_branch__state_t *b = apr_pcalloc(result_pool, sizeof(*b));

  b->vtable = apr_pmemdup(result_pool, vtable, sizeof(*vtable));

  b->vtable->vpriv.cancel_func = cancel_func;
  b->vtable->vpriv.cancel_baton = cancel_baton;

#ifdef ENABLE_ORDERING_CHECK
  b->vtable->vpriv.within_callback = FALSE;
  b->vtable->vpriv.finished = FALSE;
  b->vtable->vpriv.state_pool = result_pool;
#endif

  return b;
}

/* Create a new branch state object.
 *
 * It will have no elements (not even a root element).
 */
static svn_branch__state_t *
branch_state_create(const char *bid,
                    int root_eid,
                    svn_branch__txn_t *txn,
                    apr_pool_t *result_pool)
{
  static const svn_branch__state_vtable_t vtable = {
    {0},
    branch_state_get_elements,
    branch_state_get_element,
    branch_state_set_element,
    branch_state_copy_one,
    branch_state_copy_tree,
    branch_state_purge,
    branch_state_get_history,
    branch_state_set_history,
  };
  svn_branch__state_t *b
    = svn_branch__state_create(&vtable, NULL, NULL, result_pool);

  b->priv = apr_pcalloc(result_pool, sizeof(*b->priv));
  b->bid = apr_pstrdup(result_pool, bid);
  b->txn = txn;
  b->priv->element_tree = svn_element__tree_create(NULL, root_eid, result_pool);
  assert_branch_state_invariants(b, result_pool);
  b->priv->is_flat = TRUE;
  b->priv->history = svn_branch__history_create_empty(result_pool);
  return b;
}

/*
 * ========================================================================
 * Parsing and Serializing
 * ========================================================================
 */

svn_string_t *
svn_branch__get_default_r0_metadata(apr_pool_t *result_pool)
{
  static const char *default_repos_info
    = "r0: eids 0 1 branches 1\n"
      "B0 root-eid 0 num-eids 1\n"
      "history: parents 0\n"
      "e0: normal -1 .\n";

  return svn_string_create(default_repos_info, result_pool);
}

/*  */
static svn_error_t *
parse_branch_line(char *bid_p,
                  int *root_eid_p,
                  int *num_eids_p,
                  svn_stream_t *stream,
                  apr_pool_t *result_pool,
                  apr_pool_t *scratch_pool)
{
  svn_stringbuf_t *line;
  svn_boolean_t eof;
  int n;

  /* Read a line */
  SVN_ERR(svn_stream_readline(stream, &line, "\n", &eof, scratch_pool));
  SVN_ERR_ASSERT(!eof);

  n = sscanf(line->data, "%s root-eid %d num-eids %d",
             bid_p, root_eid_p, num_eids_p);
  SVN_ERR_ASSERT(n == 3);

  return SVN_NO_ERROR;
}

/* Parse the history metadata for BRANCH.
 */
static svn_error_t *
history_parse(svn_branch__history_t **history_p,
              svn_stream_t *stream,
              apr_pool_t *result_pool,
              apr_pool_t *scratch_pool)
{
  svn_branch__history_t *history
    = svn_branch__history_create_empty(result_pool);
  svn_stringbuf_t *line;
  svn_boolean_t eof;
  int n;
  int num_parents;
  int i;

  /* Read a line */
  SVN_ERR(svn_stream_readline(stream, &line, "\n", &eof, scratch_pool));
  SVN_ERR_ASSERT(!eof);

  n = sscanf(line->data, "history: parents %d",
             &num_parents);
  SVN_ERR_ASSERT(n == 1);

  for (i = 0; i < num_parents; i++)
    {
      svn_revnum_t rev;
      char bid[100];

      SVN_ERR(svn_stream_readline(stream, &line, "\n", &eof, scratch_pool));
      SVN_ERR_ASSERT(!eof);

      n = sscanf(line->data, "parent: r%ld.%99s",
                 &rev, bid);
      SVN_ERR_ASSERT(n == 2);

      svn_hash_sets(history->parents,
                    apr_pstrdup(result_pool, bid),
                    svn_branch__rev_bid_create(rev, bid, result_pool));
    }

  if (history_p)
    *history_p = history;
  return SVN_NO_ERROR;
}

/* Parse the mapping for one element.
 */
static svn_error_t *
parse_element_line(int *eid_p,
                   svn_boolean_t *is_subbranch_p,
                   int *parent_eid_p,
                   const char **name_p,
                   svn_stream_t *stream,
                   apr_pool_t *result_pool,
                   apr_pool_t *scratch_pool)
{
  svn_stringbuf_t *line;
  svn_boolean_t eof;
  char kind[10];
  int n;
  int offset;

  /* Read a line */
  SVN_ERR(svn_stream_readline(stream, &line, "\n", &eof, scratch_pool));
  SVN_ERR_ASSERT(!eof);

  n = sscanf(line->data, "e%d: %9s %d%n",
             eid_p,
             kind, parent_eid_p, &offset);
  SVN_ERR_ASSERT(n >= 3);  /* C std is unclear on whether '%n' counts */
  SVN_ERR_ASSERT(line->data[offset] == ' ');

  *name_p = apr_pstrdup(result_pool, line->data + offset + 1);
  *is_subbranch_p = (strcmp(kind, "subbranch") == 0);

  if (strcmp(*name_p, "(null)") == 0)
    *name_p = NULL;
  else if (strcmp(*name_p, ".") == 0)
    *name_p = "";

  return SVN_NO_ERROR;
}

const char *
svn_branch__id_nest(const char *outer_bid,
                    int outer_eid,
                    apr_pool_t *result_pool)
{
  if (!outer_bid)
    return apr_psprintf(result_pool, "B%d", outer_eid);

  return apr_psprintf(result_pool, "%s.%d", outer_bid, outer_eid);
}

void
svn_branch__id_unnest(const char **outer_bid,
                      int *outer_eid,
                      const char *bid,
                      apr_pool_t *result_pool)
{
  char *last_dot = strrchr(bid, '.');

  if (last_dot) /* BID looks like "B3.11" or "B3.11.22" etc. */
    {
      *outer_bid = apr_pstrndup(result_pool, bid, last_dot - bid);
      *outer_eid = atoi(last_dot + 1);
    }
  else /* looks like "B0" or B22" (with no dot) */
    {
      *outer_bid = NULL;
      *outer_eid = atoi(bid + 1);
    }
}

/* Create a new branch *NEW_BRANCH, initialized
 * with info parsed from STREAM, allocated in RESULT_POOL.
 */
static svn_error_t *
svn_branch__state_parse(svn_branch__state_t **new_branch,
                       svn_branch__txn_t *txn,
                       svn_stream_t *stream,
                       apr_pool_t *result_pool,
                       apr_pool_t *scratch_pool)
{
  char bid[1000];
  int root_eid, num_eids;
  svn_branch__state_t *branch_state;
  int i;

  SVN_ERR(parse_branch_line(bid, &root_eid, &num_eids,
                            stream, scratch_pool, scratch_pool));

  branch_state = branch_state_create(bid, root_eid, txn,
                                     result_pool);

  /* Read in the merge history. */
  SVN_ERR(history_parse(&branch_state->priv->history,
                        stream, result_pool, scratch_pool));

  /* Read in the structure. Set the payload of each normal element to a
     (branch-relative) reference. */
  for (i = 0; i < num_eids; i++)
    {
      int eid, this_parent_eid;
      const char *this_name;
      svn_boolean_t is_subbranch;

      SVN_ERR(parse_element_line(&eid,
                                 &is_subbranch, &this_parent_eid, &this_name,
                                 stream, scratch_pool, scratch_pool));

      if (this_name)
        {
          svn_element__payload_t *payload;
          svn_element__content_t *element;

          if (! is_subbranch)
            {
              payload = svn_element__payload_create_ref(txn->rev, bid, eid,
                                                        result_pool);
            }
          else
            {
              payload
                = svn_element__payload_create_subbranch(result_pool);
            }
          element = svn_element__content_create(this_parent_eid,
                                                this_name, payload,
                                                scratch_pool);
          SVN_ERR(branch_state_set_element(branch_state, eid, element,
                                           scratch_pool));
        }
    }

  branch_state->priv->is_flat = TRUE;
  *new_branch = branch_state;
  return SVN_NO_ERROR;
}

svn_error_t *
svn_branch__txn_parse(svn_branch__txn_t **txn_p,
                      svn_branch__repos_t *repos,
                      svn_stream_t *stream,
                      apr_pool_t *result_pool,
                      apr_pool_t *scratch_pool)
{
  svn_branch__txn_t *txn;
  svn_revnum_t rev;
  int first_eid, next_eid;
  int num_branches;
  svn_stringbuf_t *line;
  svn_boolean_t eof;
  int n;
  int j;

  SVN_ERR(svn_stream_readline(stream, &line, "\n", &eof, scratch_pool));
  SVN_ERR_ASSERT(! eof);
  n = sscanf(line->data, "r%ld: eids %d %d "
                         "branches %d",
             &rev,
             &first_eid, &next_eid,
             &num_branches);
  SVN_ERR_ASSERT(n == 4);

  txn = branch_txn_create(repos, rev, rev - 1, result_pool);
  txn->priv->first_eid = first_eid;
  txn->priv->next_eid = next_eid;

  /* parse the branches */
  for (j = 0; j < num_branches; j++)
    {
      svn_branch__state_t *branch;

      SVN_ERR(svn_branch__state_parse(&branch, txn, stream,
                                      result_pool, scratch_pool));
      APR_ARRAY_PUSH(txn->priv->branches, void *) = branch;
    }

  *txn_p = txn;
  return SVN_NO_ERROR;
}

/* Serialize the history metadata for BRANCH.
 */
static svn_error_t *
history_serialize(svn_stream_t *stream,
                  svn_branch__history_t *history,
                  apr_pool_t *scratch_pool)
{
  apr_array_header_t *ancestors_sorted;
  int i;

  /* Write entries in sorted order for stability -- so that for example
     we can test parse-then-serialize by expecting identical output. */
  ancestors_sorted = svn_sort__hash(history->parents,
                                    svn_sort_compare_items_lexically,
                                    scratch_pool);
  SVN_ERR(svn_stream_printf(stream, scratch_pool,
                            "history: parents %d\n",
                            ancestors_sorted->nelts));
  for (i = 0; i < ancestors_sorted->nelts; i++)
    {
      svn_sort__item_t *item
        = &APR_ARRAY_IDX(ancestors_sorted, i, svn_sort__item_t);
      svn_branch__rev_bid_t *rev_bid = item->value;

      SVN_ERR(svn_stream_printf(stream, scratch_pool,
                                "parent: r%ld.%s\n",
                                rev_bid->rev, rev_bid->bid));
    }

  return SVN_NO_ERROR;
}

/* Write to STREAM a parseable representation of BRANCH.
 */
svn_error_t *
svn_branch__state_serialize(svn_stream_t *stream,
                            svn_branch__state_t *branch,
                            apr_pool_t *scratch_pool)
{
  svn_eid__hash_iter_t *ei;

  SVN_ERR_ASSERT(branch->priv->is_flat);

  SVN_ERR(svn_stream_printf(stream, scratch_pool,
                            "%s root-eid %d num-eids %d\n",
                            svn_branch__get_id(branch, scratch_pool),
                            branch->priv->element_tree->root_eid,
                            apr_hash_count(branch->priv->element_tree->e_map)));

  SVN_ERR(history_serialize(stream, branch->priv->history,
                                  scratch_pool));

  for (SVN_EID__HASH_ITER_SORTED_BY_EID(ei, branch->priv->element_tree->e_map,
                                        scratch_pool))
    {
      int eid = ei->eid;
      svn_element__content_t *element = branch_get_element(branch, eid);
      int parent_eid;
      const char *name;

      SVN_ERR_ASSERT(element);
      parent_eid = element->parent_eid;
      name = element->name[0] ? element->name : ".";
      SVN_ERR(svn_stream_printf(stream, scratch_pool,
                                "e%d: %s %d %s\n",
                                eid,
                                element ? ((! element->payload->is_subbranch_root)
                                             ? "normal" : "subbranch")
                                     : "none",
                                parent_eid, name));
    }
  return SVN_NO_ERROR;
}

/*
 * ========================================================================
 */

