You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@subversion.apache.org by ju...@apache.org on 2015/12/10 18:03:33 UTC

svn commit: r1719113 - in /subversion/trunk: subversion/include/private/ subversion/libsvn_delta/ subversion/tests/cmdline/ tools/dev/svnmover/

Author: julianfoad
Date: Thu Dec 10 17:03:33 2015
New Revision: 1719113

URL: http://svn.apache.org/viewvc?rev=1719113&view=rev
Log:
In 'svnmover', track history better.

For each version of each branch, store pointers to previous branch-versions,
called "parents" like in Git. Usually, a branch version has one parent that
points to the previous version of the same branch. Upon branching, the new
branch gets one parent that points to the chosen version of the source
branch. Upon merging, in the usual case of a complete merge from a source
branch into a target branch, the target branch gets two parents: its own
previous version, and the merge source.

* subversion/include/private/svn_branch.h
  (svn_branch__txn_open_branch,
   svn_branch__txn_add_new_branch): Don't take a 'predecessor' parameter.
  (svn_branch__state_t): Don't track a 'predecessor' as a public field here.
  (svn_branch__history_t,
   svn_branch__history_create_empty,
   svn_branch__history_create,
   svn_branch__history_dup): New.
  (svn_branch__state_get_history): Rename from
    'svn_branch__state_get_merge_ancestor'.
  (svn_branch__state_set_history): Rename from
    'svn_branch__state_add_merge_ancestor'.

* subversion/include/private/svn_branch_impl.h
  Track the changes in svn_branch.h.

* subversion/libsvn_delta/branch.c
  Implement the changes in svn_branch.h.

* subversion/libsvn_delta/branch_compat.c
  Track the changes in svn_branch.h.

* subversion/libsvn_delta/branch_nested.c
  Track the changes in svn_branch.h.

* subversion/tests/cmdline/svnmover_tests.py
  (reported_mg_diff): No longer expect any diff output describing a merge
    history difference, as we don't print that in a diff any more.

* tools/dev/svnmover/ra.c
  (branch_get_mutable_state): Initialize each branch's parent to point to
    the same branch in the base revision.

* tools/dev/svnmover/svnmover.c
  (list_parents,
   history_str,
   svn_branch__history_add_parent): New.
  (history_diff): Rename from 'merge_history_diff'.
  (txn_is_changed): Don't compare history, but leave some place-holder code
    in case we want to do so later.
  (get_union_of_subbranches): Make robust against either or both inputs
    being null.
  (svn_branch__replay): Update the replaying of a change of history.
  (update_wc_base_r): Update the copying of history for a new branch.
  (do_merge): Update the recording of history.
  (do_auto_merge): Dummy implementation: does nothing.
  (show_history_r): New.
  (branch_diff_r): Don't show history differences.
  (find_branch_main_parent): New.
  (svn_branch__find_predecessor_el_rev): Update the finding of the main
    parent.
  (do_log): Also show the history before the diff of each revision.
  (do_mkbranch): Track the API changes.
  (do_branch): Initialize the history (parent) of the new branch.
  (show_branch_history): New.
  (execute): In the 'info-wc' subcommand, show the history of the working
    branch (and not of the base branch).

Modified:
    subversion/trunk/subversion/include/private/svn_branch.h
    subversion/trunk/subversion/include/private/svn_branch_impl.h
    subversion/trunk/subversion/libsvn_delta/branch.c
    subversion/trunk/subversion/libsvn_delta/branch_compat.c
    subversion/trunk/subversion/libsvn_delta/branch_nested.c
    subversion/trunk/subversion/tests/cmdline/svnmover_tests.py
    subversion/trunk/tools/dev/svnmover/ra.c
    subversion/trunk/tools/dev/svnmover/svnmover.c

Modified: subversion/trunk/subversion/include/private/svn_branch.h
URL: http://svn.apache.org/viewvc/subversion/trunk/subversion/include/private/svn_branch.h?rev=1719113&r1=1719112&r2=1719113&view=diff
==============================================================================
--- subversion/trunk/subversion/include/private/svn_branch.h (original)
+++ subversion/trunk/subversion/include/private/svn_branch.h Thu Dec 10 17:03:33 2015
@@ -211,18 +211,14 @@ svn_branch__txn_new_eid(svn_branch__txn_
  * This method returns a mutable 'branch state' object which is a part of
  * the txn.
  *
- * When adding a new branch, PREDECESSOR and ROOT_EID are used.
+ * When adding a new branch, ROOT_EID is used.
  *
- * ### When opening ('finding') an existing branch, they must match it
- *     (else throw an error)? But this is problematic: we should't care
- *     exactly where it was branched from, as long as it was branched from
- *     ... roughly 'the right branch'? ... perhaps meaning one with a common
- *     ancestor?
+ * ### When opening ('finding') an existing branch, ROOT_EID should match
+ *     it. (Should we check, and throw an error if not?)
  */
 svn_error_t *
 svn_branch__txn_open_branch(svn_branch__txn_t *txn,
                             svn_branch__state_t **new_branch_p,
-                            svn_branch__rev_bid_t *predecessor,
                             const char *new_branch_id,
                             int root_eid,
                             apr_pool_t *result_pool,
@@ -326,10 +322,6 @@ struct svn_branch__state_t
   /* The branch identifier (starting with 'B') */
   const char *bid;
 
-  /* The previous location in the lifeline of this branch. */
-  /* (REV = -1 means "in this txn") */
-  svn_branch__rev_bid_t *predecessor;
-
   /* The revision to which this branch state belongs */
   svn_branch__txn_t *txn;
 
@@ -410,7 +402,6 @@ svn_branch__txn_add_branch(svn_branch__t
 svn_branch__state_t *
 svn_branch__txn_add_new_branch(svn_branch__txn_t *txn,
                                const char *bid,
-                               svn_branch__rev_bid_t *predecessor,
                                int root_eid,
                                apr_pool_t *scratch_pool);
 
@@ -497,6 +488,24 @@ svn_boolean_t
 svn_branch__rev_bid_equal(const svn_branch__rev_bid_t *id1,
                           const svn_branch__rev_bid_t *id2);
 
+typedef struct svn_branch__history_t
+{
+  /* The immediate parents of this state in the branch/merge graph.
+     Hash of (BID -> svn_branch__rev_bid_t). */
+  apr_hash_t *parents;
+} svn_branch__history_t;
+
+svn_branch__history_t *
+svn_branch__history_create_empty(apr_pool_t *result_pool);
+
+svn_branch__history_t *
+svn_branch__history_create(apr_hash_t *parents,
+                           apr_pool_t *result_pool);
+
+svn_branch__history_t *
+svn_branch__history_dup(const svn_branch__history_t *old,
+                        apr_pool_t *result_pool);
+
 /* Return the mapping of elements in branch BRANCH.
  */
 svn_error_t *
@@ -609,26 +618,19 @@ svn_error_t *
 svn_branch__state_purge(svn_branch__state_t *branch,
                         apr_pool_t *scratch_pool);
 
-/* Get the merge ancestor(s).
+/* Get the merge history of BRANCH.
  */
 svn_error_t *
-svn_branch__state_get_merge_ancestor(svn_branch__state_t *branch,
-                                     svn_branch__rev_bid_t **merge_ancestor_p,
-                                     apr_pool_t *result_pool);
+svn_branch__state_get_history(svn_branch__state_t *branch,
+                              svn_branch__history_t **merge_history_p,
+                              apr_pool_t *result_pool);
 
-/* Set a merge ancestor.
- *
- * Currently only one is allowed; this overwrites it if it was already set.
- *
- * TODO: Allow adding multiple ancestors on different branches. When
- * there is an existing ancestor that is earlier along the same branch
- * (line of history) as MERGE_ANCESTOR, then update (replace) it instead
- * of just adding another one.
+/* Set the merge history of BRANCH.
  */
 svn_error_t *
-svn_branch__state_add_merge_ancestor(svn_branch__state_t *branch,
-                                     const svn_branch__rev_bid_t *merge_ancestor,
-                                     apr_pool_t *scratch_pool);
+svn_branch__state_set_history(svn_branch__state_t *branch,
+                              const svn_branch__history_t *merge_history,
+                              apr_pool_t *scratch_pool);
 
 /* Return the branch-relative path of element EID in BRANCH.
  *

Modified: subversion/trunk/subversion/include/private/svn_branch_impl.h
URL: http://svn.apache.org/viewvc/subversion/trunk/subversion/include/private/svn_branch_impl.h?rev=1719113&r1=1719112&r2=1719113&view=diff
==============================================================================
--- subversion/trunk/subversion/include/private/svn_branch_impl.h (original)
+++ subversion/trunk/subversion/include/private/svn_branch_impl.h Thu Dec 10 17:03:33 2015
@@ -67,7 +67,6 @@ typedef svn_error_t *(*svn_branch__txn_v
 typedef svn_branch__state_t *(*svn_branch__txn_v_add_new_branch_t)(
   svn_branch__txn_t *txn,
   const char *bid,
-  svn_branch__rev_bid_t *predecessor,
   int root_eid,
   apr_pool_t *scratch_pool);
 
@@ -89,7 +88,6 @@ typedef svn_error_t *(*svn_branch__txn_v
 typedef svn_error_t *(*svn_branch__txn_v_open_branch_t)(
   svn_branch__txn_t *txn,
   svn_branch__state_t **new_branch_p,
-  svn_branch__rev_bid_t *predecessor,
   const char *new_branch_id,
   int root_eid,
   apr_pool_t *result_pool,
@@ -186,14 +184,14 @@ typedef svn_error_t *(*svn_branch__state
   svn_branch__state_t *branch,
   apr_pool_t *scratch_pool);
 
-typedef svn_error_t *(*svn_branch__state_v_get_merge_ancestor_t)(
+typedef svn_error_t *(*svn_branch__state_v_get_history_t)(
   svn_branch__state_t *branch,
-  svn_branch__rev_bid_t **merge_ancestor_p,
+  svn_branch__history_t **history_p,
   apr_pool_t *scratch_pool);
 
-typedef svn_error_t *(*svn_branch__state_v_add_merge_ancestor_t)(
+typedef svn_error_t *(*svn_branch__state_v_set_history_t)(
   svn_branch__state_t *branch,
-  const svn_branch__rev_bid_t *merge_ancestor,
+  const svn_branch__history_t *history,
   apr_pool_t *scratch_pool);
 
 struct svn_branch__state_vtable_t
@@ -206,8 +204,8 @@ struct svn_branch__state_vtable_t
   svn_branch__state_v_copy_one_t copy_one;
   svn_branch__state_v_copy_tree_t copy_tree;
   svn_branch__state_v_purge_t purge;
-  svn_branch__state_v_get_merge_ancestor_t get_merge_ancestor;
-  svn_branch__state_v_add_merge_ancestor_t add_merge_ancestor;
+  svn_branch__state_v_get_history_t get_history;
+  svn_branch__state_v_set_history_t set_history;
 
 };
 

Modified: subversion/trunk/subversion/libsvn_delta/branch.c
URL: http://svn.apache.org/viewvc/subversion/trunk/subversion/libsvn_delta/branch.c?rev=1719113&r1=1719112&r2=1719113&view=diff
==============================================================================
--- subversion/trunk/subversion/libsvn_delta/branch.c (original)
+++ subversion/trunk/subversion/libsvn_delta/branch.c Thu Dec 10 17:03:33 2015
@@ -69,10 +69,8 @@ struct svn_branch__state_priv_t
   /* EID -> svn_element__content_t mapping. */
   svn_element__tree_t *element_tree;
 
-  /* Youngest ancestor, with respect to a complete merge, on another branch.
-     (REV = -1 means "in this txn".)
-     ### TODO: Multiple ancestors, corresponding to multiple source branches. */
-  svn_branch__rev_bid_t *merge_ancestor;
+  /* Merge history for this branch state. */
+  svn_branch__history_t *history;
 
   svn_boolean_t is_flat;
 
@@ -80,7 +78,6 @@ struct svn_branch__state_priv_t
 
 static svn_branch__state_t *
 branch_state_create(const char *bid,
-                    svn_branch__rev_bid_t *predecessor,
                     int root_eid,
                     svn_branch__txn_t *txn,
                     apr_pool_t *result_pool);
@@ -161,7 +158,6 @@ branch_txn_add_branch(svn_branch__txn_t
 static svn_branch__state_t *
 branch_txn_add_new_branch(svn_branch__txn_t *txn,
                           const char *bid,
-                          svn_branch__rev_bid_t *predecessor,
                           int root_eid,
                           apr_pool_t *scratch_pool)
 {
@@ -169,7 +165,7 @@ branch_txn_add_new_branch(svn_branch__tx
 
   SVN_ERR_ASSERT_NO_RETURN(root_eid != -1);
 
-  new_branch = branch_state_create(bid, predecessor, root_eid, txn,
+  new_branch = branch_state_create(bid, root_eid, txn,
                                    txn->priv->branches->pool);
 
   APR_ARRAY_PUSH(txn->priv->branches, void *) = new_branch;
@@ -227,7 +223,6 @@ branch_txn_new_eid(svn_branch__txn_t *tx
 static svn_error_t *
 branch_txn_open_branch(svn_branch__txn_t *txn,
                        svn_branch__state_t **new_branch_p,
-                       svn_branch__rev_bid_t *predecessor,
                        const char *new_branch_id,
                        int root_eid,
                        apr_pool_t *result_pool,
@@ -246,7 +241,6 @@ branch_txn_open_branch(svn_branch__txn_t
     {
       new_branch = svn_branch__txn_add_new_branch(txn,
                                                   new_branch_id,
-                                                  predecessor,
                                                   root_eid, scratch_pool);
     }
 
@@ -264,7 +258,6 @@ branch_txn_branch(svn_branch__txn_t *txn
                   apr_pool_t *result_pool,
                   apr_pool_t *scratch_pool)
 {
-  svn_branch__rev_bid_t *predecessor;
   svn_branch__state_t *new_branch;
   svn_branch__state_t *from_branch;
   svn_element__tree_t *from_subtree;
@@ -295,10 +288,8 @@ branch_txn_branch(svn_branch__txn_t *txn
                                from->rev, from->bid, from->eid);
     }
 
-  predecessor = svn_branch__rev_bid_create(from->rev, from->bid, scratch_pool);
   new_branch = svn_branch__txn_add_new_branch(txn,
                                               new_branch_id,
-                                              predecessor,
                                               from->eid, scratch_pool);
 
   /* Populate the mapping from the 'from' source */
@@ -377,13 +368,12 @@ svn_branch__txn_add_branch(svn_branch__t
 svn_branch__state_t *
 svn_branch__txn_add_new_branch(svn_branch__txn_t *txn,
                                const char *bid,
-                               svn_branch__rev_bid_t *predecessor,
                                int root_eid,
                                apr_pool_t *scratch_pool)
 {
   svn_branch__state_t *new_branch
     = txn->vtable->add_new_branch(txn,
-                                  bid, predecessor, root_eid,
+                                  bid, root_eid,
                                   scratch_pool);
 
   return new_branch;
@@ -425,7 +415,6 @@ svn_branch__txn_new_eid(svn_branch__txn_
 svn_error_t *
 svn_branch__txn_open_branch(svn_branch__txn_t *txn,
                             svn_branch__state_t **new_branch_p,
-                            svn_branch__rev_bid_t *predecessor,
                             const char *new_branch_id,
                             int root_eid,
                             apr_pool_t *result_pool,
@@ -433,7 +422,7 @@ svn_branch__txn_open_branch(svn_branch__
 {
   SVN_ERR(txn->vtable->open_branch(txn,
                                    new_branch_p,
-                                   predecessor, new_branch_id,
+                                   new_branch_id,
                                    root_eid, result_pool,
                                    scratch_pool));
   return SVN_NO_ERROR;
@@ -643,11 +632,6 @@ branch_txn_serialize(svn_branch__txn_t *
     {
       svn_branch__state_t *branch = APR_ARRAY_IDX(branches, i, void *);
 
-      if (branch->predecessor && branch->predecessor->rev < 0)
-        {
-          branch->predecessor->rev = txn->rev;
-        }
-
       SVN_ERR(svn_branch__state_serialize(stream, branch, scratch_pool));
     }
   return SVN_NO_ERROR;
@@ -941,6 +925,55 @@ svn_branch__rev_bid_equal(const svn_bran
           && 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;
+}
+
 
 /*
  * ========================================================================
@@ -1082,25 +1115,28 @@ branch_state_purge(svn_branch__state_t *
 
 /* An #svn_branch__state_t method. */
 static svn_error_t *
-branch_state_get_merge_ancestor(svn_branch__state_t *branch,
-                                svn_branch__rev_bid_t **merge_ancestor_p,
-                                apr_pool_t *result_pool)
+branch_state_get_history(svn_branch__state_t *branch,
+                         svn_branch__history_t **history_p,
+                         apr_pool_t *result_pool)
 {
-  *merge_ancestor_p = svn_branch__rev_bid_dup(branch->priv->merge_ancestor,
-                                              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_add_merge_ancestor(svn_branch__state_t *branch,
-                                const svn_branch__rev_bid_t *merge_ancestor,
-                                apr_pool_t *scratch_pool)
+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->merge_ancestor = svn_branch__rev_bid_dup(merge_ancestor,
-                                                         branch_pool);
+  branch->priv->history
+    = svn_branch__history_dup(history, branch_pool);
   return SVN_NO_ERROR;
 }
 
@@ -1328,24 +1364,26 @@ svn_branch__state_purge(svn_branch__stat
 }
 
 svn_error_t *
-svn_branch__state_get_merge_ancestor(svn_branch__state_t *branch,
-                                     svn_branch__rev_bid_t **merge_ancestor_p,
-                                     apr_pool_t *result_pool)
+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_merge_ancestor(branch,
-                                             merge_ancestor_p,
-                                             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_add_merge_ancestor(svn_branch__state_t *branch,
-                                     const svn_branch__rev_bid_t *merge_ancestor,
-                                     apr_pool_t *scratch_pool)
+svn_branch__state_set_history(svn_branch__state_t *branch,
+                              const svn_branch__history_t *history,
+                              apr_pool_t *scratch_pool)
 {
-  SVN_ERR(branch->vtable->add_merge_ancestor(branch,
-                                             merge_ancestor,
-                                             scratch_pool));
+  SVN_ERR_ASSERT(history);
+  SVN_ERR(branch->vtable->set_history(branch,
+                                      history,
+                                      scratch_pool));
   return SVN_NO_ERROR;
 }
 
@@ -1377,7 +1415,6 @@ svn_branch__state_create(const svn_branc
  */
 static svn_branch__state_t *
 branch_state_create(const char *bid,
-                    svn_branch__rev_bid_t *predecessor,
                     int root_eid,
                     svn_branch__txn_t *txn,
                     apr_pool_t *result_pool)
@@ -1390,19 +1427,19 @@ branch_state_create(const char *bid,
     branch_state_copy_one,
     branch_state_copy_tree,
     branch_state_purge,
-    branch_state_get_merge_ancestor,
-    branch_state_add_merge_ancestor,
+    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->predecessor = svn_branch__rev_bid_dup(predecessor, result_pool);
   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;
 }
 
@@ -1418,7 +1455,7 @@ svn_branch__get_default_r0_metadata(apr_
   static const char *default_repos_info
     = "r0: eids 0 1 branches 1\n"
       "B0 root-eid 0 num-eids 1\n"
-      "merge-history: merge-ancestors 0\n"
+      "history: parents 0\n"
       "e0: normal -1 .\n";
 
   return svn_string_create(default_repos_info, result_pool);
@@ -1429,7 +1466,6 @@ static svn_error_t *
 parse_branch_line(char *bid_p,
                   int *root_eid_p,
                   int *num_eids_p,
-                  svn_branch__rev_bid_t **predecessor,
                   svn_stream_t *stream,
                   apr_pool_t *result_pool,
                   apr_pool_t *scratch_pool)
@@ -1437,52 +1473,43 @@ parse_branch_line(char *bid_p,
   svn_stringbuf_t *line;
   svn_boolean_t eof;
   int n;
-  svn_revnum_t pred_rev;
-  char pred_bid[1000];
 
   /* 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 from r%ld.%s",
-             bid_p, root_eid_p, num_eids_p, &pred_rev, pred_bid);
-  SVN_ERR_ASSERT(n == 3 || n == 5);
-
-  if (n == 5)
-    {
-      *predecessor = svn_branch__rev_bid_create(pred_rev, pred_bid, result_pool);
-    }
-  else
-    {
-      *predecessor = NULL;
-    }
+  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 merge history for BRANCH.
+/* Parse the history metadata for BRANCH.
  */
 static svn_error_t *
-merge_history_parse(svn_branch__state_t *branch_state,
-                    svn_stream_t *stream,
-                    apr_pool_t *result_pool,
-                    apr_pool_t *scratch_pool)
+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_merge_ancestors;
+  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, "merge-history: merge-ancestors %d",
-             &num_merge_ancestors);
+  n = sscanf(line->data, "history: parents %d",
+             &num_parents);
   SVN_ERR_ASSERT(n == 1);
 
-  for (i = 0; i < num_merge_ancestors; i++)
+  for (i = 0; i < num_parents; i++)
     {
       svn_revnum_t rev;
       char bid[100];
@@ -1490,14 +1517,17 @@ merge_history_parse(svn_branch__state_t
       SVN_ERR(svn_stream_readline(stream, &line, "\n", &eof, scratch_pool));
       SVN_ERR_ASSERT(!eof);
 
-      n = sscanf(line->data, "merge-ancestor: r%ld.%99s",
+      n = sscanf(line->data, "parent: r%ld.%99s",
                  &rev, bid);
       SVN_ERR_ASSERT(n == 2);
 
-      branch_state->priv->merge_ancestor
-        = svn_branch__rev_bid_create(rev, bid, result_pool);
+      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;
 }
 
@@ -1582,19 +1612,18 @@ svn_branch__state_parse(svn_branch__stat
 {
   char bid[1000];
   int root_eid, num_eids;
-  svn_branch__rev_bid_t *predecessor;
   svn_branch__state_t *branch_state;
   int i;
 
-  SVN_ERR(parse_branch_line(bid, &root_eid, &num_eids, &predecessor,
+  SVN_ERR(parse_branch_line(bid, &root_eid, &num_eids,
                             stream, scratch_pool, scratch_pool));
 
-  branch_state = branch_state_create(bid, predecessor, root_eid, txn,
+  branch_state = branch_state_create(bid, root_eid, txn,
                                      result_pool);
 
   /* Read in the merge history. */
-  SVN_ERR(merge_history_parse(branch_state,
-                              stream, result_pool, scratch_pool));
+  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. */
@@ -1679,25 +1708,33 @@ svn_branch__txn_parse(svn_branch__txn_t
   return SVN_NO_ERROR;
 }
 
-/* Serialize the merge history information for BRANCH.
+/* Serialize the history metadata for BRANCH.
  */
 static svn_error_t *
-merge_history_serialize(svn_stream_t *stream,
-                        svn_branch__state_t *branch,
-                        apr_pool_t *scratch_pool)
+history_serialize(svn_stream_t *stream,
+                  svn_branch__history_t *history,
+                  apr_pool_t *scratch_pool)
 {
-  int num_merge_ancestors = (branch->priv->merge_ancestor) ? 1 : 0;
+  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,
-                            "merge-history: merge-ancestors %d\n",
-                            num_merge_ancestors));
-  for (i = 0; i < num_merge_ancestors; i++)
-    {
+                            "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,
-                                "merge-ancestor: r%ld.%s\n",
-                                branch->priv->merge_ancestor->rev,
-                                branch->priv->merge_ancestor->bid));
+                                "parent: r%ld.%s\n",
+                                rev_bid->rev, rev_bid->bid));
     }
 
   return SVN_NO_ERROR;
@@ -1710,27 +1747,18 @@ svn_branch__state_serialize(svn_stream_t
                             svn_branch__state_t *branch,
                             apr_pool_t *scratch_pool)
 {
-  const char *predecessor_str = "";
   svn_eid__hash_iter_t *ei;
 
   SVN_ERR_ASSERT(branch->priv->is_flat);
 
-  if (branch->predecessor)
-    {
-      assert(SVN_IS_VALID_REVNUM(branch->predecessor->rev));
-      predecessor_str = apr_psprintf(scratch_pool, " from r%ld.%s",
-                                     branch->predecessor->rev,
-                                     branch->predecessor->bid);
-    }
-
   SVN_ERR(svn_stream_printf(stream, scratch_pool,
-                            "%s root-eid %d num-eids %d%s\n",
+                            "%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),
-                            predecessor_str));
+                            apr_hash_count(branch->priv->element_tree->e_map)));
 
-  SVN_ERR(merge_history_serialize(stream, branch, scratch_pool));
+  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))

Modified: subversion/trunk/subversion/libsvn_delta/branch_compat.c
URL: http://svn.apache.org/viewvc/subversion/trunk/subversion/libsvn_delta/branch_compat.c?rev=1719113&r1=1719112&r2=1719113&view=diff
==============================================================================
--- subversion/trunk/subversion/libsvn_delta/branch_compat.c (original)
+++ subversion/trunk/subversion/libsvn_delta/branch_compat.c Thu Dec 10 17:03:33 2015
@@ -1801,14 +1801,13 @@ compat_branch_txn_add_branch(svn_branch_
 static svn_branch__state_t *
 compat_branch_txn_add_new_branch(svn_branch__txn_t *txn,
                                  const char *bid,
-                                 svn_branch__rev_bid_t *predecessor,
                                  int root_eid,
                                  apr_pool_t *scratch_pool)
 {
   /* Just forwarding: nothing more is needed. */
   svn_branch__state_t *new_branch
     = svn_branch__txn_add_new_branch(txn->priv->txn,
-                                     bid, predecessor, root_eid,
+                                     bid, root_eid,
                                      scratch_pool);
 
   return new_branch;
@@ -1868,7 +1867,6 @@ compat_branch_txn_finalize_eids(svn_bran
 static svn_error_t *
 compat_branch_txn_open_branch(svn_branch__txn_t *txn,
                               svn_branch__state_t **new_branch_p,
-                              svn_branch__rev_bid_t *predecessor,
                               const char *new_branch_id,
                               int root_eid,
                               apr_pool_t *result_pool,
@@ -1876,7 +1874,7 @@ compat_branch_txn_open_branch(svn_branch
 {
   /* Just forwarding: nothing more is needed. */
   SVN_ERR(svn_branch__txn_open_branch(txn->priv->txn,
-                                      new_branch_p, predecessor,
+                                      new_branch_p,
                                       new_branch_id, root_eid,
                                       result_pool,
                                       scratch_pool));

Modified: subversion/trunk/subversion/libsvn_delta/branch_nested.c
URL: http://svn.apache.org/viewvc/subversion/trunk/subversion/libsvn_delta/branch_nested.c?rev=1719113&r1=1719112&r2=1719113&view=diff
==============================================================================
--- subversion/trunk/subversion/libsvn_delta/branch_nested.c (original)
+++ subversion/trunk/subversion/libsvn_delta/branch_nested.c Thu Dec 10 17:03:33 2015
@@ -209,8 +209,6 @@ svn_branch__get_subtree(svn_branch__stat
                                                       result_pool);
   new_subtree
     = svn_branch__subtree_create(element_tree->e_map, eid, result_pool);
-  new_subtree->predecessor = svn_branch__rev_bid_dup(branch->predecessor,
-                                                     result_pool);
 
   /* Add subbranches */
   SVN_ERR(svn_branch__get_immediate_subbranch_eids(branch, &subbranch_eids,
@@ -303,7 +301,6 @@ svn_branch__instantiate_elements_r(svn_b
                                             scratch_pool);
         new_branch = svn_branch__txn_add_new_branch(to_branch->txn,
                                                     new_branch_id,
-                                                    this_subtree->predecessor,
                                                     this_subtree->tree->root_eid,
                                                     scratch_pool);
 
@@ -480,14 +477,13 @@ nested_branch_txn_add_branch(svn_branch_
 static svn_branch__state_t *
 nested_branch_txn_add_new_branch(svn_branch__txn_t *txn,
                                  const char *bid,
-                                 svn_branch__rev_bid_t *predecessor,
                                  int root_eid,
                                  apr_pool_t *scratch_pool)
 {
   /* Just forwarding: nothing more is needed. */
   svn_branch__state_t *new_branch
     = svn_branch__txn_add_new_branch(txn->priv->wrapped_txn,
-                                     bid, predecessor, root_eid,
+                                     bid, root_eid,
                                      scratch_pool);
 
   return new_branch;
@@ -539,7 +535,6 @@ nested_branch_txn_new_eid(svn_branch__tx
 static svn_error_t *
 nested_branch_txn_open_branch(svn_branch__txn_t *txn,
                               svn_branch__state_t **new_branch_p,
-                              svn_branch__rev_bid_t *predecessor,
                               const char *new_branch_id,
                               int root_eid,
                               apr_pool_t *result_pool,
@@ -547,10 +542,10 @@ nested_branch_txn_open_branch(svn_branch
 {
   /* Just forwarding: nothing more is needed. */
   SVN_ERR(svn_branch__txn_open_branch(txn->priv->wrapped_txn,
-                                      new_branch_p, predecessor,
+                                      new_branch_p,
                                       new_branch_id, root_eid,
                                       result_pool,
-                                     scratch_pool));
+                                      scratch_pool));
   return SVN_NO_ERROR;
 }
 

Modified: subversion/trunk/subversion/tests/cmdline/svnmover_tests.py
URL: http://svn.apache.org/viewvc/subversion/trunk/subversion/tests/cmdline/svnmover_tests.py?rev=1719113&r1=1719112&r2=1719113&view=diff
==============================================================================
--- subversion/trunk/subversion/tests/cmdline/svnmover_tests.py (original)
+++ subversion/trunk/subversion/tests/cmdline/svnmover_tests.py Thu Dec 10 17:03:33 2015
@@ -563,7 +563,7 @@ def reported_br_params(path1, path2):
   return subbranch_rpath, subbranch_fullpath
 
 def reported_mg_diff():
-  return [r'--- .*merge history.*']
+  return []  #[r'--- history ...']
 
 def reported_br_diff(path1, path2=None):
   """Return expected header lines for diff of a branch, or subtree in a branch.

Modified: subversion/trunk/tools/dev/svnmover/ra.c
URL: http://svn.apache.org/viewvc/subversion/trunk/tools/dev/svnmover/ra.c?rev=1719113&r1=1719112&r2=1719113&view=diff
==============================================================================
--- subversion/trunk/tools/dev/svnmover/ra.c (original)
+++ subversion/trunk/tools/dev/svnmover/ra.c Thu Dec 10 17:03:33 2015
@@ -299,11 +299,18 @@ branch_get_mutable_state(svn_branch__txn
   for (i = 0; i < branches->nelts; i++)
     {
       svn_branch__state_t *b = APR_ARRAY_IDX(branches, i, void *);
+      svn_branch__history_t *history
+        = svn_branch__history_create_empty(result_pool);
 
-      b->predecessor
+      /* Set each branch's parent to the branch in the base rev */
+      svn_branch__rev_bid_t *parent
         = svn_branch__rev_bid_create(base_revision,
                                      svn_branch__get_id(b, scratch_pool),
                                      result_pool);
+
+      svn_hash_sets(history->parents,
+                    apr_pstrdup(result_pool, b->bid), parent);
+      SVN_ERR(svn_branch__state_set_history(b, history, scratch_pool));
     }
 
   *txn_p = txn;

Modified: subversion/trunk/tools/dev/svnmover/svnmover.c
URL: http://svn.apache.org/viewvc/subversion/trunk/tools/dev/svnmover/svnmover.c?rev=1719113&r1=1719112&r2=1719113&view=diff
==============================================================================
--- subversion/trunk/tools/dev/svnmover/svnmover.c (original)
+++ subversion/trunk/tools/dev/svnmover/svnmover.c Thu Dec 10 17:03:33 2015
@@ -539,31 +539,115 @@ rev_bid_str(const svn_branch__rev_bid_t
   return apr_psprintf(result_pool, "r%ld.%s", rev_bid->rev, rev_bid->bid);
 }
 
+/*  */
+static const char *
+list_parents(svn_branch__history_t *history,
+             apr_pool_t *result_pool)
+{
+  const char *result = "";
+  apr_hash_index_t *hi;
+
+  for (hi = apr_hash_first(result_pool, history->parents);
+       hi; hi = apr_hash_next(hi))
+    {
+      svn_branch__rev_bid_t *parent = apr_hash_this_val(hi);
+      const char *parent_str = rev_bid_str(parent, result_pool);
+
+      result = apr_psprintf(result_pool, "%s%s%s",
+                            result, result[0] ? ", " : "", parent_str);
+    }
+  return result;
+}
+
+/* Return a string representation of HISTORY.
+ */
+static const char *
+history_str(svn_branch__history_t *history,
+            apr_pool_t *result_pool)
+{
+  const char *result
+    = list_parents(history, result_pool);
+
+  return apr_psprintf(result_pool, "parents={%s}", result);
+}
+
+/*
+ */
+static svn_error_t *
+svn_branch__history_add_parent(svn_branch__history_t *history,
+                               svn_revnum_t rev,
+                               const char *branch_id,
+                               apr_pool_t *scratch_pool)
+{
+  apr_pool_t *pool = apr_hash_pool_get(history->parents);
+  svn_branch__rev_bid_t *new_parent;
+
+  new_parent = svn_branch__rev_bid_create(rev, branch_id, pool);
+  svn_hash_sets(history->parents, apr_pstrdup(pool, branch_id), new_parent);
+  return SVN_NO_ERROR;
+}
+
 /* Set *DIFFERENCE_P to some sort of indication of the difference between
- * MERGE_HISTORY1 and MERGE_HISTORY2, or to null if there is no difference.
+ * HISTORY1 and HISTORY2, or to null if there is no difference.
  *
  * Inputs may be null.
  */
 static svn_error_t *
-merge_history_diff(const char **difference_p,
-                   svn_branch__rev_bid_t *merge_history1,
-                   svn_branch__rev_bid_t *merge_history2,
-                   apr_pool_t *result_pool)
-{
-  *difference_p = NULL;
-  if ((merge_history1 || merge_history2)
-      && !(merge_history1 && merge_history2
-           && svn_branch__rev_bid_equal(merge_history1, merge_history2)))
+history_diff(const char **difference_p,
+             svn_branch__history_t *history1,
+             svn_branch__history_t *history2,
+             apr_pool_t *result_pool,
+             apr_pool_t *scratch_pool)
+{
+  apr_hash_t *combined;
+  apr_hash_index_t *hi;
+  svn_boolean_t different = FALSE;
+
+  if (! history1)
+    history1 = svn_branch__history_create_empty(scratch_pool);
+  if (! history2)
+    history2 = svn_branch__history_create_empty(scratch_pool);
+  combined = hash_overlay(history1->parents,
+                          history2->parents);
+
+  for (hi = apr_hash_first(scratch_pool, combined);
+       hi; hi = apr_hash_next(hi))
+    {
+      const char *bid = apr_hash_this_key(hi);
+      svn_branch__rev_bid_t *parent1 = svn_hash_gets(history1->parents, bid);
+      svn_branch__rev_bid_t *parent2 = svn_hash_gets(history2->parents, bid);
+
+      if (!(parent1 && parent2
+            && svn_branch__rev_bid_equal(parent1, parent2)))
+        {
+          different = TRUE;
+          break;
+        }
+    }
+  if (different)
     {
       *difference_p = apr_psprintf(result_pool, "%s -> %s",
-                                   rev_bid_str(merge_history1, result_pool),
-                                   rev_bid_str(merge_history2, result_pool));
+                                   history_str(history1, scratch_pool),
+                                   history_str(history2, scratch_pool));
+    }
+  else
+    {
+      *difference_p = NULL;
     }
   return SVN_NO_ERROR;
 }
 
 /* Set *IS_CHANGED to true if EDIT_TXN differs from its base txn, else to
  * false.
+ *
+ * Notice only a difference in content: branches deleted or added, or branch
+ * contents different. Ignore any differences in branch history metadata.
+ *
+ * ### At least we must ignore the "this branch" parent changing from
+ *     old-revision to new-revision. However we should probably notice
+ *     if a merge parent is added (which means we want to make a commit
+ *     recording this merge, even if no content changed), and perhaps
+ *     other cases.
  */
 static svn_error_t *
 txn_is_changed(svn_branch__txn_t *edit_txn,
@@ -602,9 +686,6 @@ txn_is_changed(svn_branch__txn_t *edit_t
       svn_branch__state_t *base_branch
         = svn_branch__txn_get_branch_by_id(base_txn, edit_branch->bid,
                                            scratch_pool);
-      svn_branch__rev_bid_t *edit_branch_merge_history;
-      svn_branch__rev_bid_t *base_branch_merge_history;
-      const char *merge_history_difference;
       svn_element__tree_t *edit_branch_elements, *base_branch_elements;
       apr_hash_t *diff;
 
@@ -614,20 +695,29 @@ txn_is_changed(svn_branch__txn_t *edit_t
           return SVN_NO_ERROR;
         }
 
-      /* Compare merge histories */
-      SVN_ERR(svn_branch__state_get_merge_ancestor(
-                edit_branch, &edit_branch_merge_history, scratch_pool));
-      SVN_ERR(svn_branch__state_get_merge_ancestor(
-                base_branch, &base_branch_merge_history, scratch_pool));
-      SVN_ERR(merge_history_diff(&merge_history_difference,
-                                 edit_branch_merge_history,
-                                 base_branch_merge_history,
-                                 scratch_pool));
-      if (merge_history_difference)
+#if 0
+      /* Compare histories */
+      /* ### No, don't. Ignore any differences in branch history metadata. */
+      {
+      svn_branch__history_t *edit_branch_history;
+      svn_branch__history_t *base_branch_history;
+      const char *history_difference;
+
+      SVN_ERR(svn_branch__state_get_history(edit_branch, &edit_branch_history,
+                                            scratch_pool));
+      SVN_ERR(svn_branch__state_get_history(base_branch, &base_branch_history,
+                                            scratch_pool));
+      SVN_ERR(history_diff(&history_difference,
+                           edit_branch_history,
+                           base_branch_history,
+                           scratch_pool, scratch_pool));
+      if (history_difference)
         {
           *is_changed = TRUE;
           return SVN_NO_ERROR;
         }
+      }
+#endif
 
       /* Compare elements */
       SVN_ERR(svn_branch__state_get_elements(edit_branch, &edit_branch_elements,
@@ -726,8 +816,11 @@ get_union_of_subbranches(apr_hash_t **al
                                     svn_branch__root_eid(right_branch),
                                     result_pool));
   all_subbranches
-    = left_branch ? hash_overlay(s_left->subbranches, s_right->subbranches)
-                  : s_right->subbranches;
+    = (s_left && s_right) ? hash_overlay(s_left->subbranches,
+                                         s_right->subbranches)
+        : s_left ? s_left->subbranches
+        : s_right ? s_right->subbranches
+        : apr_hash_make(result_pool);
 
   *all_subbranches_p = all_subbranches;
   return SVN_NO_ERROR;
@@ -765,6 +858,31 @@ svn_branch__replay(svn_branch__txn_t *ed
          element where it was attached */
     }
 
+  /* Replay any change in history */
+  /* ### Actually, here we just set the output history to the right-hand-side
+     history if that differs from left-hand-side.
+     This doesn't seem right, in general. It's OK if we're just copying
+     a txn into a fresh txn, as for example we do during commit. */
+  {
+    svn_branch__history_t *left_history = NULL;
+    svn_branch__history_t *right_history = NULL;
+    const char *history_difference;
+
+    if (left_branch)
+      SVN_ERR(svn_branch__state_get_history(left_branch, &left_history,
+                                            scratch_pool));
+    if (right_branch)
+      SVN_ERR(svn_branch__state_get_history(right_branch, &right_history,
+                                            scratch_pool));
+    SVN_ERR(history_diff(&history_difference, left_history, right_history,
+                         scratch_pool, scratch_pool));
+    if (history_difference)
+      {
+        SVN_ERR(svn_branch__state_set_history(edit_branch, right_history,
+                                              scratch_pool));
+      }
+  }
+
   /* Replay its subbranches, recursively.
      (If we're deleting the current branch, we don't also need to
      explicitly delete its subbranches... do we?) */
@@ -797,7 +915,6 @@ svn_branch__replay(svn_branch__txn_t *ed
                 = svn_branch__id_nest(edit_branch->bid, this_eid, scratch_pool);
 
               SVN_ERR(svn_branch__txn_open_branch(edit_txn, &edit_subbranch,
-                                                  right_subbranch->predecessor,
                                                   new_branch_id,
                                                   svn_branch__root_eid(right_subbranch),
                                                   scratch_pool, scratch_pool));
@@ -813,27 +930,6 @@ svn_branch__replay(svn_branch__txn_t *ed
         }
     }
 
-  /* Replay any change in merge history */
-  {
-    svn_branch__rev_bid_t *left_merge_history = NULL;
-    svn_branch__rev_bid_t *right_merge_history = NULL;
-    const char *merge_history_difference;
-
-    if (left_branch)
-      SVN_ERR(svn_branch__state_get_merge_ancestor(
-                left_branch, &left_merge_history, scratch_pool));
-    if (right_branch)
-      SVN_ERR(svn_branch__state_get_merge_ancestor(
-                right_branch, &right_merge_history, scratch_pool));
-    SVN_ERR(merge_history_diff(&merge_history_difference,
-              left_merge_history, right_merge_history, scratch_pool));
-    if (merge_history_difference)
-      {
-        SVN_ERR(svn_branch__state_add_merge_ancestor(
-                  edit_branch, right_merge_history, scratch_pool));
-      }
-  }
-
   return SVN_NO_ERROR;
 }
 
@@ -993,13 +1089,17 @@ update_wc_base_r(svnmover_wc_t *wc,
             {
               const char *new_branch_id
                 = svn_branch__id_nest(base_branch->bid, eid, scratch_pool);
+              svn_branch__history_t *history;
 
               SVN_ERR(svn_branch__txn_open_branch(base_branch->txn,
                                                   &base_subbranch,
-                                                  work_subbranch->predecessor,
                                                   new_branch_id,
                                                   svn_branch__root_eid(work_subbranch),
                                                   scratch_pool, scratch_pool));
+              SVN_ERR(svn_branch__state_get_history(
+                        work_subbranch, &history, scratch_pool));
+              SVN_ERR(svn_branch__state_set_history(
+                        base_subbranch, history, scratch_pool));
             }
           SVN_ERR(update_wc_base_r(wc, base_subbranch, work_subbranch,
                                    new_rev, scratch_pool));
@@ -1785,7 +1885,7 @@ do_merge(svnmover_wc_t *wc,
          svn_branch__el_rev_id_t *yca,
          apr_pool_t *scratch_pool)
 {
-  svn_branch__rev_bid_t *new_ancestor;
+  svn_branch__history_t *history;
 
   if (src->eid != tgt->eid || src->eid != yca->eid)
     {
@@ -1799,15 +1899,15 @@ do_merge(svnmover_wc_t *wc,
                                 src, tgt, yca,
                                 wc->pool, scratch_pool));
 
-  /* Update the merge history */
+  /* Update the history */
+  SVN_ERR(svn_branch__state_get_history(tgt->branch, &history, scratch_pool));
   /* ### Assume this was a complete merge -- i.e. all changes up to YCA were
-     previously merged, so now SRC is a new ancestor. */
-  new_ancestor = svn_branch__rev_bid_create(src->rev, src->branch->bid,
-                                              scratch_pool);
-  SVN_ERR(svn_branch__state_add_merge_ancestor(wc->working->branch, new_ancestor,
-                                               scratch_pool));
-  svnmover_notify_v(_("--- recorded merge ancestor as: %ld.%s"),
-                    new_ancestor->rev, new_ancestor->bid);
+     previously merged, so now SRC is a new parent. */
+  SVN_ERR(svn_branch__history_add_parent(history, src->rev, src->branch->bid,
+                                         scratch_pool));
+  SVN_ERR(svn_branch__state_set_history(tgt->branch, history, scratch_pool));
+  svnmover_notify_v(_("--- recorded merge parent as: %ld.%s"),
+                    src->rev, src->branch->bid);
 
   if (svnmover_any_conflicts(wc->conflicts))
     {
@@ -1827,8 +1927,10 @@ do_auto_merge(svnmover_wc_t *wc,
 {
   svn_branch__rev_bid_t *yca;
 
-  SVN_ERR(svn_branch__state_get_merge_ancestor(tgt->branch, &yca,
-                                               scratch_pool));
+  /* Find the Youngest Common Ancestor.
+     ### TODO */
+  yca = NULL;
+
   if (yca)
     {
       svn_branch__repos_t *repos = wc->working->branch->txn->repos;
@@ -1854,6 +1956,48 @@ do_auto_merge(svnmover_wc_t *wc,
   return SVN_NO_ERROR;
 }
 
+/* Show the difference in history metadata between BRANCH1 and BRANCH2.
+ *
+ * If HEADER is non-null, print *HEADER and then set *HEADER to null.
+ *
+ * BRANCH1 and/or BRANCH2 may be null.
+ */
+static svn_error_t *
+show_history_r(svn_branch__state_t *branch,
+               const char *prefix,
+               apr_pool_t *scratch_pool)
+{
+  svn_branch__history_t *history = NULL;
+  svn_branch__subtree_t *subtree = NULL;
+  apr_hash_index_t *hi;
+
+  if (! branch)
+    return SVN_NO_ERROR;
+
+  SVN_ERR(svn_branch__state_get_history(branch, &history, scratch_pool));
+  svnmover_notify("%s%s: %s", prefix,
+                  branch->bid, history_str(history, scratch_pool));
+
+  /* recurse into each subbranch */
+  SVN_ERR(svn_branch__get_subtree(branch, &subtree,
+                                  svn_branch__root_eid(branch),
+                                  scratch_pool));
+   for (hi = apr_hash_first(scratch_pool, subtree->subbranches);
+       hi; hi = apr_hash_next(hi))
+    {
+      int e = svn_eid__hash_this_key(hi);
+      svn_branch__state_t *subbranch = NULL;
+
+      SVN_ERR(svn_branch__get_subbranch_at_eid(branch, &subbranch, e,
+                                               scratch_pool));
+      if (subbranch)
+        {
+          SVN_ERR(show_history_r(subbranch, prefix, scratch_pool));
+        }
+    }
+  return SVN_NO_ERROR;
+}
+
 /*  */
 typedef struct diff_item_t
 {
@@ -2182,21 +2326,6 @@ branch_diff_r(svn_branch__el_rev_id_t *l
               const char *prefix,
               apr_pool_t *scratch_pool)
 {
-  svn_branch__rev_bid_t *merge_history1, *merge_history2;
-  const char *merge_history_difference;
-
-  /* ### This should be done for each branch, e.g. in subtree_diff_r(). */
-  /* ### This notification should start with a '--- diff branch ...' line. */
-  SVN_ERR(svn_branch__state_get_merge_ancestor(left->branch, &merge_history1,
-                                               scratch_pool));
-  SVN_ERR(svn_branch__state_get_merge_ancestor(right->branch, &merge_history2,
-                                               scratch_pool));
-  SVN_ERR(merge_history_diff(&merge_history_difference,
-                             merge_history1, merge_history2, scratch_pool));
-  if (merge_history_difference)
-    svnmover_notify("%s--- merge history is different: %s", prefix,
-                    merge_history_difference);
-
   SVN_ERR(subtree_diff_r(left->branch, left->eid,
                          right->branch, right->eid,
                          diff_func, prefix, scratch_pool));
@@ -2380,8 +2509,40 @@ do_cat(svn_branch__el_rev_id_t *file_el_
   return SVN_NO_ERROR;
 }
 
+/* Find the main parent of branch-state BRANCH. That means:
+ *   - the only parent (in the case of straight history or branching), else
+ *   - the parent with the same branch id (in the case of normal merging), else
+ *   - none (in the case of a new unrelated branch, or a new branch formed
+ *     by merging two or more other branches).
+ */
+static svn_error_t *
+find_branch_main_parent(svn_branch__state_t *branch,
+                        svn_branch__rev_bid_t **predecessor_p,
+                        apr_pool_t *result_pool)
+{
+  svn_branch__history_t *history;
+  svn_branch__rev_bid_t *our_own_history;
+  svn_branch__rev_bid_t *predecessor = NULL;
+
+  SVN_ERR(svn_branch__state_get_history(branch, &history, result_pool));
+  if (apr_hash_count(history->parents) == 1)
+    {
+      apr_hash_index_t *hi = apr_hash_first(result_pool, history->parents);
+
+      predecessor = apr_hash_this_val(hi);
+    }
+  else if ((our_own_history = svn_hash_gets(history->parents, branch->bid)))
+    {
+      predecessor = our_own_history;
+    }
+
+  if (predecessor_p)
+    *predecessor_p = predecessor;
+  return SVN_NO_ERROR;
+}
+
 /* Set *NEW_EL_REV_P to the location where OLD_EL_REV was in the previous
- * revision. Branching is followed.
+ * revision. Follow the "main line" of any branching in its history.
  *
  * If the same EID...
  */
@@ -2391,24 +2552,17 @@ svn_branch__find_predecessor_el_rev(svn_
                                     apr_pool_t *result_pool)
 {
   const svn_branch__repos_t *repos = old_el_rev->branch->txn->repos;
-  svn_branch__rev_bid_t *predecessor = old_el_rev->branch->predecessor;
+  svn_branch__rev_bid_t *predecessor;
   svn_branch__state_t *branch;
 
+  SVN_ERR(find_branch_main_parent(old_el_rev->branch,
+                                  &predecessor, result_pool));
   if (! predecessor)
     {
       *new_el_rev_p = NULL;
       return SVN_NO_ERROR;
     }
 
-  /* A predecessor can point at another branch within the same revision.
-     We don't want that result, so iterate until we find another revision. */
-  while (predecessor->rev == old_el_rev->rev)
-    {
-      branch = svn_branch__txn_get_branch_by_id(
-                 old_el_rev->branch->txn, predecessor->bid, result_pool);
-      predecessor = branch->predecessor;
-    }
-
   SVN_ERR(svn_branch__repos_get_branch_by_id(&branch,
                                              repos, predecessor->rev,
                                              predecessor->bid, result_pool));
@@ -2437,6 +2591,8 @@ do_log(svn_branch__el_rev_id_t *left,
 
       svnmover_notify(SVN_CL__LOG_SEP_STRING "r%ld | ...",
                       right->rev);
+      svnmover_notify("History:");
+      SVN_ERR(show_history_r(right->branch, "   ", scratch_pool));
       svnmover_notify("Changed elements:");
       SVN_ERR(branch_diff_r(el_rev_left, right,
                             show_subtree_diff, "   ",
@@ -2477,8 +2633,8 @@ do_mkbranch(const char **new_branch_id_p
   new_branch_id = svn_branch__id_nest(outer_branch_id, new_outer_eid,
                                       scratch_pool);
   SVN_ERR(svn_branch__txn_open_branch(txn, &new_branch,
-                                      NULL /*predecessor*/, new_branch_id,
-                                      new_inner_eid, scratch_pool, scratch_pool));
+                                      new_branch_id, new_inner_eid,
+                                      scratch_pool, scratch_pool));
   SVN_ERR(svn_branch__state_alter_one(new_branch, new_inner_eid,
                                       -1, "", payload, scratch_pool));
 
@@ -2517,6 +2673,7 @@ do_branch(svn_branch__state_t **new_bran
   int to_outer_eid;
   const char *new_branch_id;
   svn_branch__state_t *new_branch;
+  svn_branch__history_t *history;
   const char *to_path
     = branch_peid_name_to_path(to_outer_branch,
                                to_outer_parent_eid, new_name, scratch_pool);
@@ -2529,6 +2686,10 @@ do_branch(svn_branch__state_t **new_bran
   SVN_ERR(svn_branch__txn_branch(txn, &new_branch,
                                  from, new_branch_id,
                                  result_pool, scratch_pool));
+  history = svn_branch__history_create_empty(scratch_pool);
+  SVN_ERR(svn_branch__history_add_parent(history, from->rev, from->bid,
+                                         scratch_pool));
+  SVN_ERR(svn_branch__state_set_history(new_branch, history, scratch_pool));
   SVN_ERR(svn_branch__state_alter_one(to_outer_branch, to_outer_eid,
                                       to_outer_parent_eid, new_name,
                                       svn_element__payload_create_subbranch(
@@ -3065,6 +3226,45 @@ do_migrate(svnmover_wc_t *wc,
   return SVN_NO_ERROR;
 }
 
+static svn_error_t *
+show_branch_history(svn_branch__state_t *branch,
+                    apr_pool_t *scratch_pool)
+{
+  svn_branch__history_t *history;
+  svn_branch__rev_bid_t *main_parent;
+  apr_hash_index_t *hi;
+
+  SVN_ERR(svn_branch__state_get_history(branch, &history, scratch_pool));
+
+  SVN_ERR(find_branch_main_parent(branch, &main_parent, scratch_pool));
+  if (main_parent)
+    {
+      if (strcmp(main_parent->bid, branch->bid) == 0)
+        {
+          svnmover_notify("  main parent: r%ld.%s",
+                          main_parent->rev, main_parent->bid);
+        }
+      else
+        {
+          svnmover_notify("  main parent (branched from): r%ld.%s",
+                          main_parent->rev, main_parent->bid);
+        }
+    }
+  for (hi = apr_hash_first(scratch_pool, history->parents);
+       hi; hi = apr_hash_next(hi))
+    {
+      svn_branch__rev_bid_t *parent = apr_hash_this_val(hi);
+
+      if (! svn_branch__rev_bid_equal(parent, main_parent))
+        {
+          svnmover_notify("  other parent (complete merge): r%ld.%s",
+                          parent->rev, parent->bid);
+        }
+    }
+
+  return SVN_NO_ERROR;
+}
+
 /* Show info about element E.
  *
  * TODO: Show different info for a repo element versus a WC element.
@@ -3253,14 +3453,11 @@ execute(svnmover_wc_t *wc,
           {
             svn_boolean_t is_modified;
             svn_revnum_t base_rev_min, base_rev_max;
-            svn_branch__rev_bid_t *merge_ancestor;
 
             SVN_ERR(txn_is_changed(wc->working->branch->txn, &is_modified,
                                    iterpool));
             SVN_ERR(svnmover_wc_get_base_revs(wc, &base_rev_min, &base_rev_max,
                                               iterpool));
-            SVN_ERR(svn_branch__state_get_merge_ancestor(
-                      wc->working->branch, &merge_ancestor, iterpool));
 
             svnmover_notify("Repository Root: %s", wc->repos_root_url);
             if (base_rev_min == base_rev_max)
@@ -3269,16 +3466,9 @@ execute(svnmover_wc_t *wc,
               svnmover_notify("Base Revisions: %ld to %ld",
                               base_rev_min, base_rev_max);
             svnmover_notify("Base Branch:    %s", wc->base->branch->bid);
-            SVN_ERR(svn_branch__state_get_merge_ancestor(
-                      wc->base->branch, &merge_ancestor, iterpool));
-            if (merge_ancestor)
-              svnmover_notify("  merge ancestor: %ld.%s",
-                              merge_ancestor->rev, merge_ancestor->bid);
             svnmover_notify("Working Branch: %s", wc->working->branch->bid);
+            SVN_ERR(show_branch_history(wc->working->branch, iterpool));
             svnmover_notify("Modified:       %s", is_modified ? "yes" : "no");
-            if (merge_ancestor)
-              svnmover_notify("  merge ancestor: %ld.%s",
-                              merge_ancestor->rev, merge_ancestor->bid);
           }
           break;