You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@subversion.apache.org by pb...@apache.org on 2012/03/21 03:39:00 UTC

svn commit: r1303257 [2/4] - in /subversion/branches/inheritable-props: ./ build/ac-macros/ notes/ notes/directory-index/ notes/wc-ng/ subversion/bindings/swig/ruby/test/ subversion/include/ subversion/include/private/ subversion/libsvn_client/ subvers...

Modified: subversion/branches/inheritable-props/subversion/libsvn_client/merge.c
URL: http://svn.apache.org/viewvc/subversion/branches/inheritable-props/subversion/libsvn_client/merge.c?rev=1303257&r1=1303256&r2=1303257&view=diff
==============================================================================
--- subversion/branches/inheritable-props/subversion/libsvn_client/merge.c (original)
+++ subversion/branches/inheritable-props/subversion/libsvn_client/merge.c Wed Mar 21 02:38:58 2012
@@ -165,6 +165,14 @@ typedef struct url_uuid_t
   const char *uuid;
 } url_uuid_t;
 
+/* A location in a repository. */
+typedef struct repo_location_t
+{
+  url_uuid_t *repo;
+  svn_revnum_t rev;
+  const char *url;
+} repo_location_t;
+
 /* */
 typedef struct merge_source_t
 {
@@ -187,14 +195,10 @@ typedef struct merge_target_t
   /* Node kind of the WC node (at the start of the merge) */
   svn_node_kind_t kind;
 
-  /* URL of the node, or NULL if node is locally added */
-  const char *url;
-
-  /* Revision of the node, or SVN_INVALID_REVNUM if node is locally added */
-  svn_revnum_t rev;
-
-  /* Repository root URL and UUID, even if node is locally added */
-  url_uuid_t repos_root;
+  /* The repository location of the base node of the target WC.  If the node
+   * is locally added, then URL & REV are NULL & SVN_INVALID_REVNUM.  REPO
+   * is always valid. */
+  repo_location_t loc;
 
 } merge_target_t;
 
@@ -332,12 +336,12 @@ check_repos_match(merge_cmd_baton_t *mer
                   const char *url,
                   apr_pool_t *scratch_pool)
 {
-  if (!svn_uri__is_ancestor(merge_b->target->repos_root.url, url))
+  if (!svn_uri__is_ancestor(merge_b->target->loc.repo->url, url))
     return svn_error_createf(
         SVN_ERR_UNSUPPORTED_FEATURE, NULL,
          _("Url '%s' of '%s' is not in repository '%s'"),
          url, svn_dirent_local_style(local_abspath, scratch_pool),
-         merge_b->target->repos_root.url);
+         merge_b->target->loc.repo->url);
 
   return SVN_NO_ERROR;
 }
@@ -1734,13 +1738,13 @@ merge_file_added(svn_wc_notify_state_t *
          merge-from-foreign-repository scenario) or wrong place in the
          right repository (in the same-repos scenario).  So we'll
          strip them.  (Is this a layering violation?)  */
-      if (svn_property_kind(NULL, prop->name) == svn_prop_wc_kind)
+      if (svn_property_kind2(prop->name) == svn_prop_wc_kind)
         continue;
 
       /* And in the foreign repository merge case, we only want
          regular properties. */
       if ((! merge_b->same_repos)
-          && (svn_property_kind(NULL, prop->name) != svn_prop_regular_kind))
+          && (svn_property_kind2(prop->name) != svn_prop_regular_kind))
         continue;
 
       /* Issue #3383: We don't want mergeinfo from a foreign repository. */
@@ -7765,7 +7769,7 @@ record_mergeinfo_for_dir_merge(svn_merge
               svn_mergeinfo_t subtree_history_as_mergeinfo;
               apr_array_header_t *child_merge_src_rangelist;
               const char *subtree_mergeinfo_url =
-                svn_path_url_add_component2(merge_b->target->repos_root.url,
+                svn_path_url_add_component2(merge_b->target->loc.repo->url,
                                             child_merge_src_fspath + 1,
                                             iterpool);
 
@@ -7964,7 +7968,7 @@ record_mergeinfo_for_added_subtrees(
              take the intersection of the naive mergeinfo with
              MERGEINFO_PATH/rel_added_path's history. */
           added_path_mergeinfo_url =
-            svn_path_url_add_component2(merge_b->target->repos_root.url,
+            svn_path_url_add_component2(merge_b->target->loc.repo->url,
                                         added_path_mergeinfo_fspath + 1,
                                         iterpool);
           SVN_ERR(svn_client__get_history_as_mergeinfo(
@@ -8294,11 +8298,11 @@ remove_noop_subtree_ranges(const merge_s
   SVN_ERR(svn_client__path_relative_to_root(
                     &(log_gap_baton.target_fspath), merge_b->ctx->wc_ctx,
                     merge_b->target->abspath,
-                    merge_b->target->repos_root.url, TRUE, NULL,
+                    merge_b->target->loc.repo->url, TRUE, NULL,
                     result_pool, scratch_pool));
   SVN_ERR(svn_client__path_relative_to_root(
                     &(log_gap_baton.source_fspath), merge_b->ctx->wc_ctx,
-                    source->url2, merge_b->target->repos_root.url, TRUE, NULL,
+                    source->url2, merge_b->target->loc.repo->url, TRUE, NULL,
                     result_pool, scratch_pool));
   log_gap_baton.merged_ranges = apr_array_make(scratch_pool, 0,
                                                sizeof(svn_revnum_t *));
@@ -9248,29 +9252,82 @@ ensure_wc_path_has_repo_revision(const c
   return SVN_NO_ERROR;
 }
 
-/* Set *TARGET to a new, fully initialized, target description structure. */
+/* "Open" the target WC for a merge.  That means:
+ *   - find out its node kind
+ *   - find out its exact repository location
+ *   - check the WC for suitability (throw an error if unsuitable)
+ *
+ * Set *TARGET_P to a new, fully initialized, target description structure.
+ *
+ * ALLOW_MIXED_REV, ALLOW_LOCAL_MODS, ALLOW_SWITCHED_SUBTREES determine
+ * whether the WC is deemed suitable; see ensure_wc_is_suitable_merge_target()
+ * for details.
+ *
+ * If the node is locally added, the rev and URL will be null/invalid. Some
+ * kinds of merge can use such a target; others can't.
+ */
 static svn_error_t *
-target_node_location(merge_target_t **target_p,
-                     const char *wc_abspath,
-                     svn_client_ctx_t *ctx,
-                     apr_pool_t *result_pool,
-                     apr_pool_t *scratch_pool)
+open_target_wc(merge_target_t **target_p,
+               const char *wc_abspath,
+               svn_boolean_t allow_mixed_rev,
+               svn_boolean_t allow_local_mods,
+               svn_boolean_t allow_switched_subtrees,
+               svn_client_ctx_t *ctx,
+               apr_pool_t *result_pool,
+               apr_pool_t *scratch_pool)
 {
   merge_target_t *target = apr_palloc(result_pool, sizeof(*target));
 
   target->abspath = apr_pstrdup(result_pool, wc_abspath);
+
   SVN_ERR(svn_wc_read_kind(&target->kind, ctx->wc_ctx, wc_abspath, FALSE,
                            scratch_pool));
-  SVN_ERR(svn_client__wc_node_get_origin(&target->repos_root.url,
-                                         &target->repos_root.uuid,
-                                         &target->rev, &target->url,
+
+  target->loc.repo = apr_palloc(result_pool, sizeof(*target->loc.repo));
+  SVN_ERR(svn_client__wc_node_get_origin(&target->loc.repo->url,
+                                         &target->loc.repo->uuid,
+                                         &target->loc.rev, &target->loc.url,
                                          wc_abspath, ctx,
                                          result_pool, scratch_pool));
 
+  SVN_ERR(ensure_wc_is_suitable_merge_target(
+            wc_abspath, ctx,
+            allow_mixed_rev, allow_local_mods, allow_switched_subtrees,
+            scratch_pool));
+
   *target_p = target;
   return SVN_NO_ERROR;
 }
 
+/* Open an RA session to PATH_OR_URL at PEG_REVISION.  Set *RA_SESSION_P to
+ * the session and set *LOCATION_P to the resolved revision, URL and
+ * repository root.  Allocate the results in RESULT_POOL.  */
+static svn_error_t *
+open_source_session(repo_location_t **location_p,
+                    svn_ra_session_t **ra_session_p,
+                    const char *path_or_url,
+                    const svn_opt_revision_t *peg_revision,
+                    svn_client_ctx_t *ctx,
+                    apr_pool_t *result_pool,
+                    apr_pool_t *scratch_pool)
+{
+  repo_location_t *location = apr_palloc(result_pool, sizeof(*location));
+  svn_ra_session_t *ra_session;
+
+  SVN_ERR(svn_client__ra_session_from_path(
+            &ra_session, &location->rev, &location->url,
+            path_or_url, NULL, peg_revision, peg_revision,
+            ctx, result_pool));
+  location->repo = apr_palloc(result_pool, sizeof(*location->repo));
+  SVN_ERR(svn_ra_get_repos_root2(ra_session, &location->repo->url,
+                                 result_pool));
+  SVN_ERR(svn_ra_get_uuid2(ra_session, &location->repo->uuid, result_pool));
+
+  *location_p = location;
+  *ra_session_p = ra_session;
+  return SVN_NO_ERROR;
+}
+
 
 /*-----------------------------------------------------------------------*/
 
@@ -9294,9 +9351,9 @@ merge_locked(const char *source1,
              apr_pool_t *scratch_pool)
 {
   merge_target_t *target;
+  repo_location_t *source1_loc, *source2_loc;
   merge_source_t source;
   svn_boolean_t related = FALSE, ancestral = FALSE;
-  url_uuid_t source_repos_root, source_repos_root2;
   svn_ra_session_t *ra_session1, *ra_session2;
   apr_array_header_t *merge_sources;
   svn_error_t *err;
@@ -9311,46 +9368,33 @@ merge_locked(const char *source1,
      ancestor of the other -- just call svn_client_merge_peg3() with
      the appropriate args. */
 
-  SVN_ERR(target_node_location(&target, target_abspath,
-                               ctx, scratch_pool, scratch_pool));
-
-  /* Do not allow merges into mixed-revision working copies. */
-  SVN_ERR(ensure_wc_is_suitable_merge_target(target->abspath, ctx,
-                                             allow_mixed_rev, TRUE, TRUE,
-                                             scratch_pool));
+  SVN_ERR(open_target_wc(&target, target_abspath,
+                         allow_mixed_rev, TRUE, TRUE,
+                         ctx, scratch_pool, scratch_pool));
 
   /* Open RA sessions to both sides of our merge source, and resolve URLs
    * and revisions. */
   sesspool = svn_pool_create(scratch_pool);
-  SVN_ERR(svn_client__ra_session_from_path(&ra_session1,
-                                           &source.rev1, &source.url1,
-                                           source1, NULL, revision1, revision1,
-                                           ctx, sesspool));
-  SVN_ERR(svn_client__ra_session_from_path(&ra_session2,
-                                           &source.rev2, &source.url2,
-                                           source2, NULL, revision2, revision2,
-                                           ctx, sesspool));
-
-  SVN_ERR(svn_ra_get_repos_root2(ra_session1, &source_repos_root.url,
-                                 sesspool));
-  SVN_ERR(svn_ra_get_uuid2(ra_session1, &source_repos_root.uuid,
-                           scratch_pool));
-  SVN_ERR(svn_ra_get_repos_root2(ra_session2, &source_repos_root2.url,
-                                 sesspool));
-  SVN_ERR(svn_ra_get_uuid2(ra_session2, &source_repos_root2.uuid,
-                           scratch_pool));
+  SVN_ERR(open_source_session(&source1_loc, &ra_session1, source1, revision1,
+                              ctx, sesspool, scratch_pool));
+  source.url1 = source1_loc->url;
+  source.rev1 = source1_loc->rev;
+  SVN_ERR(open_source_session(&source2_loc, &ra_session2, source2, revision2,
+                              ctx, sesspool, scratch_pool));
+  source.url2 = source2_loc->url;
+  source.rev2 = source2_loc->rev;
 
   /* We can't do a diff between different repositories. */
   /* ### We should also insist that the root URLs of the two sources match,
    *     as we are only carrying around a single source-repos-root from now
    *     on, and URL calculations will go wrong if they differ.
    *     Alternatively, teach the code to cope with differing root URLs. */
-  SVN_ERR(check_same_repos(&source_repos_root, source.url1,
-                           &source_repos_root2, source.url2,
+  SVN_ERR(check_same_repos(source1_loc->repo, source.url1,
+                           source2_loc->repo, source.url2,
                            FALSE /* strict_urls */, scratch_pool));
 
   /* Do our working copy and sources come from the same repository? */
-  same_repos = is_same_repos(&target->repos_root, &source_repos_root,
+  same_repos = is_same_repos(target->loc.repo, source1_loc->repo,
                              TRUE /* strict_urls */);
 
   /* Unless we're ignoring ancestry, see if the two sources are related.  */
@@ -9928,15 +9972,12 @@ find_unmerged_mergeinfo_subroutine(svn_m
    reintegrate source and the reintegrate target.
 
    SOURCE_REPOS_REL_PATH is the path of the reintegrate source relative to
-   the root of the repository.  TARGET_REPOS_REL_PATH is the path of the
-   reintegrate target relative to the root of the repository.
+   the root of the repository.
 
-   TARGET_REV is the working revision the entire WC tree rooted at
-   TARGET_REPOS_REL_PATH is at.  SOURCE_REV is the peg revision of the
-   reintegrate source.
+   SOURCE_REV is the peg revision of the reintegrate source.
 
    SOURCE_RA_SESSION is a session opened to the SOURCE_REPOS_REL_PATH
-   and TARGET_RA_SESSION is open to TARGET_REPOS_REL_PATH.
+   and TARGET_RA_SESSION is open to TARGET->loc.url.
 
    For each entry in TARGET_HISTORY_HASH check that the history it
    represents is contained in either the explicit mergeinfo for the
@@ -9964,8 +10005,7 @@ find_unmerged_mergeinfo(svn_mergeinfo_ca
                         svn_mergeinfo_catalog_t source_catalog,
                         apr_hash_t *target_history_hash,
                         const char *source_repos_rel_path,
-                        const char *target_repos_rel_path,
-                        svn_revnum_t target_rev,
+                        const merge_target_t *target,
                         svn_revnum_t source_rev,
                         svn_ra_session_t *source_ra_session,
                         svn_ra_session_t *target_ra_session,
@@ -9973,16 +10013,19 @@ find_unmerged_mergeinfo(svn_mergeinfo_ca
                         apr_pool_t *result_pool,
                         apr_pool_t *scratch_pool)
 {
+  const char *target_repos_rel_path;
   const char *source_session_url;
-  const char *target_session_url;
   apr_hash_index_t *hi;
   svn_mergeinfo_catalog_t new_catalog = apr_hash_make(result_pool);
   apr_pool_t *iterpool = svn_pool_create(scratch_pool);
 
+  SVN_ERR(svn_client__path_relative_to_root(&target_repos_rel_path,
+                                            ctx->wc_ctx, target->abspath,
+                                            NULL, FALSE, NULL,
+                                            scratch_pool, scratch_pool));
+
   *youngest_merged_rev = SVN_INVALID_REVNUM;
 
-  SVN_ERR(svn_ra_get_session_url(target_ra_session, &target_session_url,
-                                 scratch_pool));
   SVN_ERR(svn_ra_get_session_url(source_ra_session, &source_session_url,
                                  scratch_pool));
 
@@ -10106,12 +10149,12 @@ find_unmerged_mergeinfo(svn_mergeinfo_ca
 
       source_url = svn_path_url_add_component2(source_session_url,
                                                path_rel_to_session, iterpool);
-      target_url = svn_path_url_add_component2(target_session_url,
+      target_url = svn_path_url_add_component2(target->loc.url,
                                                path_rel_to_session, iterpool);
       err = svn_client__get_history_as_mergeinfo(&target_history_as_mergeinfo,
                                                  NULL /* has_rev_zero_history */,
                                                  target_url,
-                                                 target_rev, target_rev,
+                                                 target->loc.rev, target->loc.rev,
                                                  SVN_INVALID_REVNUM,
                                                  target_ra_session,
                                                  ctx, iterpool);
@@ -10143,7 +10186,7 @@ find_unmerged_mergeinfo(svn_mergeinfo_ca
           /* ### Why looking at SOURCE_url at TARGET_rev? */
           SVN_ERR(find_unmerged_mergeinfo_subroutine(
                     &filtered_mergeinfo, target_history_as_mergeinfo,
-                    source_mergeinfo, source_url, target_rev,
+                    source_mergeinfo, source_url, target->loc.rev,
                     source_ra_session, ctx, scratch_pool, iterpool));
           if (apr_hash_count(filtered_mergeinfo))
             apr_hash_set(new_catalog,
@@ -10176,30 +10219,26 @@ find_unmerged_mergeinfo(svn_mergeinfo_ca
    merge actually performs.  If no merge should be performed, set
    *URL_LEFT to NULL and *REV_LEFT to SVN_INVALID_REVNUM.
 
-   TARGET_ABSPATH is the absolute working copy path of the reintegrate
+   TARGET->abspath is the absolute working copy path of the reintegrate
    merge.
 
-   TARGET_REPOS_REL_PATH is the path of TARGET_ABSPATH relative to
-   the root of the repository.  SOURCE_REPOS_REL_PATH is the path of the
-   reintegrate source relative to the root of the repository.
+   SOURCE_LOC is the reintegrate source.
 
    SUBTREES_WITH_MERGEINFO is a hash of (const char *) absolute paths mapped
    to (svn_mergeinfo_t *) mergeinfo values for each working copy path with
-   explicit mergeinfo in TARGET_ABSPATH.  Actually we only need to know the
+   explicit mergeinfo in TARGET->abspath.  Actually we only need to know the
    paths, not the mergeinfo.
 
-   TARGET_REV is the working revision the entire WC tree rooted at
-   TARGET_REPOS_REL_PATH is at.  SOURCE_REV is the peg revision of the
-   reintegrate source.
+   TARGET->loc.rev is the working revision the entire WC tree rooted at
+   TARGET is at.
 
    Populate *UNMERGED_TO_SOURCE_CATALOG with the mergeinfo describing what
-   parts of TARGET_REPOS_REL_PATH@TARGET_REV have not been merged to
-   SOURCE_REPOS_REL_PATH@SOURCE_REV, up to the youngest revision ever merged
-   from the TARGET_ABSPATH to the source if such exists, see doc string for
-   find_unmerged_mergeinfo().
+   parts of TARGET->loc have not been merged to SOURCE_LOC, up to the
+   youngest revision ever merged from the TARGET->abspath to the source if
+   such exists, see doc string for find_unmerged_mergeinfo().
 
-   SOURCE_RA_SESSION is a session opened to the SOURCE_REPOS_REL_PATH
-   and TARGET_RA_SESSION is open to TARGET_REPOS_REL_PATH.
+   SOURCE_RA_SESSION is a session opened to the SOURCE_LOC
+   and TARGET_RA_SESSION is open to TARGET->loc.url.
 
    *URL_LEFT, *MERGED_TO_SOURCE_CATALOG , and *UNMERGED_TO_SOURCE_CATALOG are
    allocated in RESULT_POOL.  SCRATCH_POOL is used for all temporary
@@ -10209,20 +10248,15 @@ calculate_left_hand_side(const char **ur
                          svn_revnum_t *rev_left,
                          svn_mergeinfo_t *merged_to_source_catalog,
                          svn_mergeinfo_t *unmerged_to_source_catalog,
-                         const char *target_abspath,
-                         const char *target_repos_rel_path,
+                         const merge_target_t *target,
                          apr_hash_t *subtrees_with_mergeinfo,
-                         svn_revnum_t target_rev,
-                         const char *source_repos_rel_path,
-                         const char *source_repos_root,
-                         svn_revnum_t source_rev,
+                         const repo_location_t *source_loc,
                          svn_ra_session_t *source_ra_session,
                          svn_ra_session_t *target_ra_session,
                          svn_client_ctx_t *ctx,
                          apr_pool_t *result_pool,
                          apr_pool_t *scratch_pool)
 {
-  const char *target_repos_root_url = source_repos_root;  /* necessarily */
   svn_mergeinfo_catalog_t mergeinfo_catalog, unmerged_catalog;
   apr_array_header_t *source_repos_rel_path_as_array
     = apr_array_make(scratch_pool, 1, sizeof(const char *));
@@ -10233,8 +10267,7 @@ calculate_left_hand_side(const char **ur
   svn_revnum_t youngest_merged_rev;
   const char *yc_ancestor_url;
   svn_revnum_t yc_ancestor_rev;
-  const char *source_url;
-  const char *target_url;
+  const char *source_repos_rel_path;
 
   /* Initialize our return variables. */
   *url_left = NULL;
@@ -10244,9 +10277,9 @@ calculate_left_hand_side(const char **ur
      contained within SUBTREES_WITH_MERGEINFO.  If this is the case then
      add a dummy item for TARGET_ABSPATH so we get its history (i.e. implicit
      mergeinfo) below.  */
-  if (!apr_hash_get(subtrees_with_mergeinfo, target_abspath,
+  if (!apr_hash_get(subtrees_with_mergeinfo, target->abspath,
                     APR_HASH_KEY_STRING))
-    apr_hash_set(subtrees_with_mergeinfo, target_abspath,
+    apr_hash_set(subtrees_with_mergeinfo, target->abspath,
                  APR_HASH_KEY_STRING, apr_hash_make(result_pool));
 
   /* Get the history segments (as mergeinfo) for TARGET_ABSPATH and any of
@@ -10269,12 +10302,12 @@ calculate_left_hand_side(const char **ur
                                                 NULL, FALSE,
                                                 NULL, scratch_pool,
                                                 iterpool));
-      url = svn_path_url_add_component2(target_repos_root_url,
+      url = svn_path_url_add_component2(target->loc.repo->url,
                                         path_rel_to_root, iterpool);
       SVN_ERR(svn_client__get_history_as_mergeinfo(&target_history_as_mergeinfo,
                                                    NULL /* has_rev_zero_hist */,
                                                    url,
-                                                   target_rev, target_rev,
+                                                   target->loc.rev, target->loc.rev,
                                                    SVN_INVALID_REVNUM,
                                                    target_ra_session,
                                                    ctx, scratch_pool));
@@ -10287,27 +10320,21 @@ calculate_left_hand_side(const char **ur
   /* Check that SOURCE_URL@SOURCE_REV and TARGET_URL@TARGET_REV are
      actually related, we can't reintegrate if they are not.  Also
      get an initial value for YC_ANCESTOR_REV. */
-  source_url = svn_path_url_add_component2(source_repos_root,
-                                           source_repos_rel_path,
-                                           iterpool);
-  target_url = svn_path_url_add_component2(source_repos_root,
-                                           target_repos_rel_path,
-                                           iterpool);
   SVN_ERR(svn_client__get_youngest_common_ancestor(NULL, &yc_ancestor_url,
                                                    &yc_ancestor_rev,
-                                                   source_url, source_rev,
-                                                   target_url, target_rev,
+                                                   source_loc->url, source_loc->rev,
+                                                   target->loc.url, target->loc.rev,
                                                    ctx, iterpool));
   if (!(yc_ancestor_url && SVN_IS_VALID_REVNUM(yc_ancestor_rev)))
     return svn_error_createf(SVN_ERR_CLIENT_NOT_READY_TO_MERGE, NULL,
                              _("'%s@%ld' must be ancestrally related to "
-                               "'%s@%ld'"), source_url, source_rev,
-                             target_url, target_rev);
+                               "'%s@%ld'"), source_loc->url, source_loc->rev,
+                             target->loc.url, target->loc.rev);
 
   /* If the source revision is the same as the youngest common
      revision, then there can't possibly be any unmerged revisions
      that we need to apply to target. */
-  if (source_rev == yc_ancestor_rev)
+  if (source_loc->rev == yc_ancestor_rev)
     {
       svn_pool_destroy(iterpool);
       return SVN_NO_ERROR;
@@ -10317,10 +10344,12 @@ calculate_left_hand_side(const char **ur
      with differing explicit mergeinfo. */
   APR_ARRAY_PUSH(source_repos_rel_path_as_array, const char *) = "";
   SVN_ERR(svn_ra_get_mergeinfo(source_ra_session, &mergeinfo_catalog,
-                               source_repos_rel_path_as_array, source_rev,
+                               source_repos_rel_path_as_array, source_loc->rev,
                                svn_mergeinfo_inherited,
                                TRUE, iterpool));
 
+  source_repos_rel_path = svn_uri_skip_ancestor(source_loc->repo->url,
+                                                source_loc->url, scratch_pool);
   if (mergeinfo_catalog)
     SVN_ERR(svn_mergeinfo__add_prefix_to_catalog(&mergeinfo_catalog,
                                                  mergeinfo_catalog,
@@ -10342,9 +10371,8 @@ calculate_left_hand_side(const char **ur
                                   mergeinfo_catalog,
                                   target_history_hash,
                                   source_repos_rel_path,
-                                  target_repos_rel_path,
-                                  target_rev,
-                                  source_rev,
+                                  target,
+                                  source_loc->rev,
                                   source_ra_session,
                                   target_ra_session,
                                   ctx,
@@ -10369,7 +10397,7 @@ calculate_left_hand_side(const char **ur
          *URL_LEFT and *REV_LEFT to cover the youngest part of this range. */
       *rev_left = youngest_merged_rev;
       SVN_ERR(svn_client__repos_location(url_left, target_ra_session,
-                                         target_url, target_rev,
+                                         target->loc.url, target->loc.rev,
                                          youngest_merged_rev,
                                          ctx, result_pool, iterpool));
     }
@@ -10382,8 +10410,10 @@ calculate_left_hand_side(const char **ur
  * from SOURCE_PATH_OR_URL at SOURCE_PEG_REVISION into the working
  * copy at TARGET.
  *
- * Set *TARGET_RA_SESSION_P and *SOURCE_RA_SESSION_P to new RA sessions
- * opened to the target and source branches respectively.  Set *SOURCE_P to
+ * SOURCE_RA_SESSION and TARGET_RA_SESSION are RA sessions opened to the
+ * source and target branches respectively.
+ *
+ * Set *SOURCE_P to
  * the source-left and source-right locations of the required merge.  Set
  * *YC_ANCESTOR_REV_P to the revision number of the youngest ancestor.
  * Any of these output pointers may be NULL if not wanted.
@@ -10391,21 +10421,16 @@ calculate_left_hand_side(const char **ur
  * See svn_client_find_reintegrate_merge() for other details.
  */
 static svn_error_t *
-find_reintegrate_merge(svn_ra_session_t **target_ra_session_p,
-                       svn_ra_session_t **source_ra_session_p,
-                       merge_source_t **source_p,
+find_reintegrate_merge(merge_source_t **source_p,
                        svn_revnum_t *yc_ancestor_rev_p,
-                       const char *source_path_or_url,
-                       const svn_opt_revision_t *source_peg_revision,
+                       svn_ra_session_t *source_ra_session,
+                       const repo_location_t *source_loc,
+                       svn_ra_session_t *target_ra_session,
                        const merge_target_t *target,
                        svn_client_ctx_t *ctx,
                        apr_pool_t *result_pool,
                        apr_pool_t *scratch_pool)
 {
-  url_uuid_t source_repos_root;
-  svn_ra_session_t *target_ra_session;
-  svn_ra_session_t *source_ra_session;
-  const char *source_url, *source_repos_rel_path, *target_repos_rel_path;
   const char *yc_ancestor_relpath;
   svn_revnum_t yc_ancestor_rev;
   merge_source_t source;
@@ -10414,60 +10439,15 @@ find_reintegrate_merge(svn_ra_session_t 
   svn_error_t *err;
   apr_hash_t *subtrees_with_mergeinfo;
 
-  if (! target->url)
-    return svn_error_createf(SVN_ERR_CLIENT_UNRELATED_RESOURCES, NULL,
-                             _("Can't reintegrate into '%s' because it is "
-                               "locally added and therefore not related to "
-                               "the merge source"),
-                             svn_dirent_local_style(target->abspath,
-                                                    scratch_pool));
-
-  /* Make sure we're dealing with a real URL. */
-  SVN_ERR(svn_client_url_from_path2(&source_url, source_path_or_url, ctx,
-                                    scratch_pool, scratch_pool));
-  if (! source_url)
-    return svn_error_createf(SVN_ERR_ENTRY_MISSING_URL, NULL,
-                             _("'%s' has no URL"),
-                             svn_dirent_local_style(source_path_or_url,
-                                                    scratch_pool));
-
-  /* Determine the source's repository root URL. */
-  SVN_ERR(svn_client_get_repos_root(&source_repos_root.url,
-                                    &source_repos_root.uuid, source_url,
-                                    ctx, scratch_pool, scratch_pool));
-
-  /* source_repos_root and target->repos_root are required to be the same,
-     as mergeinfo doesn't come into play for cross-repository merging. */
-  SVN_ERR(check_same_repos(&source_repos_root,
-                           svn_dirent_local_style(source_path_or_url,
-                                                  scratch_pool),
-                           &target->repos_root,
-                           svn_dirent_local_style(target->abspath,
-                                                  scratch_pool),
-                           TRUE /* strict_urls */, scratch_pool));
-
-  /* A reintegrate merge requires the merge target to reflect a subtree
-   * of the repository as found at a single revision. */
-  SVN_ERR(ensure_wc_is_suitable_merge_target(target->abspath, ctx,
-                                             FALSE, FALSE, FALSE,
-                                             scratch_pool));
-
   /* As the WC tree is "pure", use its last-updated-to revision as
      the default revision for the left side of our merge, since that's
      what the repository sub-tree is required to be up to date with
      (with regard to the WC). */
   /* ### Bogus/obsolete comment? */
 
-  source_repos_rel_path = svn_uri_skip_ancestor(target->repos_root.url, source_url,
-                                                scratch_pool);
-  SVN_ERR(svn_client__path_relative_to_root(&target_repos_rel_path,
-                                            ctx->wc_ctx, target->abspath,
-                                            NULL, FALSE, NULL,
-                                            scratch_pool, scratch_pool));
-
   /* Can't reintegrate to or from the root of the repository. */
-  if (svn_path_is_empty(source_repos_rel_path)
-      || svn_path_is_empty(target_repos_rel_path))
+  if (strcmp(source_loc->url, source_loc->repo->url) == 0
+      || strcmp(target->loc.url, target->loc.repo->url) == 0)
     return svn_error_createf(SVN_ERR_CLIENT_NOT_READY_TO_MERGE, NULL,
                              _("Neither the reintegrate source nor target "
                                "can be the root of the repository"));
@@ -10482,26 +10462,12 @@ find_reintegrate_merge(svn_ra_session_t 
     err = svn_error_quick_wrap(err, _("Reintegrate merge not possible"));
   SVN_ERR(err);
 
-  /* Open two RA sessions, one to our source and one to our target. */
-  SVN_ERR(svn_client__ra_session_from_path(&source_ra_session, &source.rev2, &source.url2,
-                                           source_url, NULL, source_peg_revision,
-                                           source_peg_revision,
-                                           ctx, result_pool));
-  SVN_ERR(svn_client__open_ra_session_internal(&target_ra_session, NULL,
-                                               target->url,
-                                               NULL, NULL, FALSE, FALSE,
-                                               ctx, result_pool));
-
   SVN_ERR(calculate_left_hand_side(&source.url1, &source.rev1,
                                    &merged_to_source_mergeinfo_catalog,
                                    &unmerged_to_source_mergeinfo_catalog,
-                                   target->abspath,
-                                   target_repos_rel_path,
+                                   target,
                                    subtrees_with_mergeinfo,
-                                   target->rev,
-                                   source_repos_rel_path,
-                                   source_repos_root.url,
-                                   source.rev2,
+                                   source_loc,
                                    source_ra_session,
                                    target_ra_session,
                                    ctx,
@@ -10511,10 +10477,6 @@ find_reintegrate_merge(svn_ra_session_t 
      be performed here?  */
   if (! source.url1)
     {
-      if (target_ra_session_p)
-        *target_ra_session_p = NULL;
-      if (source_ra_session_p)
-        *source_ra_session_p = NULL;
       if (source_p)
         *source_p = NULL;
       if (yc_ancestor_rev_p)
@@ -10522,10 +10484,13 @@ find_reintegrate_merge(svn_ra_session_t 
       return SVN_NO_ERROR;
     }
 
+  source.rev2 = source_loc->rev;
+  source.url2 = source_loc->url;
+
   /* If the target was moved after the source was branched from it,
      it is possible that the left URL differs from the target's current
      URL.  If so, then adjust TARGET_RA_SESSION to point to the old URL. */
-  if (strcmp(source.url1, target->url))
+  if (strcmp(source.url1, target->loc.url))
     SVN_ERR(svn_ra_reparent(target_ra_session, source.url1, scratch_pool));
 
   SVN_ERR(svn_client__get_youngest_common_ancestor(&yc_ancestor_relpath, NULL,
@@ -10547,6 +10512,9 @@ find_reintegrate_merge(svn_ra_session_t 
          target?  If so, make sure we've merged a contiguous
          prefix. */
       svn_mergeinfo_t final_unmerged_catalog = apr_hash_make(scratch_pool);
+      const char *source_repos_rel_path
+        = svn_uri_skip_ancestor(source_loc->repo->url, source_loc->url,
+                                scratch_pool);
 
       SVN_ERR(find_unsynced_ranges(source_repos_rel_path,
                                    yc_ancestor_relpath,
@@ -10572,17 +10540,13 @@ find_reintegrate_merge(svn_ra_session_t 
                                      "reintegrate source, but this is "
                                      "not the case:\n%s"),
                                    yc_ancestor_rev + 1, source.rev2,
-                                   target->url,
+                                   target->loc.url,
                                    source_mergeinfo_cat_string->data);
         }
     }
 
   /* Left side: trunk@youngest-trunk-rev-merged-to-branch-at-specified-peg-rev
    * Right side: branch@specified-peg-revision */
-  if (target_ra_session_p)
-    *target_ra_session_p = target_ra_session;
-  if (source_ra_session_p)
-    *source_ra_session_p = source_ra_session;
   if (source_p)
     *source_p = apr_pmemdup(result_pool, &source, sizeof(source));
   if (yc_ancestor_rev_p)
@@ -10590,6 +10554,73 @@ find_reintegrate_merge(svn_ra_session_t 
   return SVN_NO_ERROR;
 }
 
+/* Resolve the source and target locations and open RA sessions to them, and
+ * perform some checks appropriate for a reintegrate merge.
+ *
+ * Set *SOURCE_RA_SESSION_P and *SOURCE_LOC_P to a new session and the
+ * repository location of SOURCE_PATH_OR_URL at SOURCE_PEG_REVISION.  Set
+ * *TARGET_RA_SESSION_P and *TARGET_P to a new session and the repository
+ * location of the WC at TARGET_ABSPATH.
+ *
+ * Throw a SVN_ERR_CLIENT_UNRELATED_RESOURCES error if the target WC node is
+ * a locally added node or if the source and target are not in the same
+ * repository.  Throw a SVN_ERR_CLIENT_NOT_READY_TO_MERGE error if the
+ * target WC is not at a single revision without switched subtrees and
+ * without local mods.
+ *
+ * Allocate all the outputs in RESULT_POOL.
+ */
+static svn_error_t *
+open_reintegrate_source_and_target(svn_ra_session_t **source_ra_session_p,
+                                   repo_location_t **source_loc_p,
+                                   svn_ra_session_t **target_ra_session_p,
+                                   merge_target_t **target_p,
+                                   const char *source_path_or_url,
+                                   const svn_opt_revision_t *source_peg_revision,
+                                   const char *target_abspath,
+                                   svn_client_ctx_t *ctx,
+                                   apr_pool_t *result_pool,
+                                   apr_pool_t *scratch_pool)
+{
+  repo_location_t *source_loc;
+  merge_target_t *target;
+
+  /* Open the target WC.  A reintegrate merge requires the merge target to
+   * reflect a subtree of the repository as found at a single revision. */
+  SVN_ERR(open_target_wc(&target, target_abspath,
+                         FALSE, FALSE, FALSE,
+                         ctx, scratch_pool, scratch_pool));
+  SVN_ERR(svn_client__open_ra_session_internal(target_ra_session_p, NULL,
+                                               target->loc.url,
+                                               NULL, NULL, FALSE, FALSE,
+                                               ctx, scratch_pool));
+  if (! target->loc.url)
+    return svn_error_createf(SVN_ERR_CLIENT_UNRELATED_RESOURCES, NULL,
+                             _("Can't reintegrate into '%s' because it is "
+                               "locally added and therefore not related to "
+                               "the merge source"),
+                             svn_dirent_local_style(target->abspath,
+                                                    scratch_pool));
+
+  SVN_ERR(open_source_session(&source_loc, source_ra_session_p,
+                              source_path_or_url, source_peg_revision,
+                              ctx, result_pool, scratch_pool));
+
+  /* source_loc->repo and target->loc.repo are required to be the same,
+     as mergeinfo doesn't come into play for cross-repository merging. */
+  SVN_ERR(check_same_repos(source_loc->repo,
+                           svn_dirent_local_style(source_path_or_url,
+                                                  scratch_pool),
+                           target->loc.repo,
+                           svn_dirent_local_style(target->abspath,
+                                                  scratch_pool),
+                           TRUE /* strict_urls */, scratch_pool));
+
+  *source_loc_p = source_loc;
+  *target_p = target;
+  return SVN_NO_ERROR;
+}
+
 svn_error_t *
 svn_client_find_reintegrate_merge(const char **url1_p,
                                   svn_revnum_t *rev1_p,
@@ -10603,16 +10634,23 @@ svn_client_find_reintegrate_merge(const 
                                   apr_pool_t *scratch_pool)
 {
   const char *target_abspath;
+  svn_ra_session_t *source_ra_session;
+  repo_location_t *source_loc;
+  svn_ra_session_t *target_ra_session;
   merge_target_t *target;
   merge_source_t *source;
 
   SVN_ERR(svn_dirent_get_absolute(&target_abspath, target_wcpath,
                                   scratch_pool));
-  SVN_ERR(target_node_location(&target, target_abspath,
-                               ctx, scratch_pool, scratch_pool));
-  SVN_ERR(find_reintegrate_merge(NULL, NULL, &source, NULL,
-                                 source_path_or_url, source_peg_revision,
-                                 target,
+
+  SVN_ERR(open_reintegrate_source_and_target(
+            &source_ra_session, &source_loc, &target_ra_session, &target,
+            source_path_or_url, source_peg_revision, target_abspath,
+            ctx, scratch_pool, scratch_pool));
+
+  SVN_ERR(find_reintegrate_merge(&source, NULL,
+                                 source_ra_session, source_loc,
+                                 target_ra_session, target,
                                  ctx, result_pool, scratch_pool));
   if (source)
     {
@@ -10643,18 +10681,20 @@ merge_reintegrate_locked(const char *sou
 {
   svn_ra_session_t *target_ra_session, *source_ra_session;
   merge_target_t *target;
+  repo_location_t *source_loc;
   merge_source_t *source;
   svn_revnum_t yc_ancestor_rev;
   svn_boolean_t use_sleep;
   svn_error_t *err;
 
-  SVN_ERR(target_node_location(&target, target_abspath,
-                               ctx, scratch_pool, scratch_pool));
-
-  SVN_ERR(find_reintegrate_merge(&target_ra_session, &source_ra_session,
-                                 &source, &yc_ancestor_rev,
-                                 source_path_or_url, source_peg_revision,
-                                 target,
+  SVN_ERR(open_reintegrate_source_and_target(
+            &source_ra_session, &source_loc, &target_ra_session, &target,
+            source_path_or_url, source_peg_revision, target_abspath,
+            ctx, scratch_pool, scratch_pool));
+
+  SVN_ERR(find_reintegrate_merge(&source, &yc_ancestor_rev,
+                                 source_ra_session, source_loc,
+                                 target_ra_session, target,
                                  ctx, scratch_pool, scratch_pool));
 
   if (! source)
@@ -10734,10 +10774,8 @@ merge_peg_locked(const char *source_path
                  apr_pool_t *scratch_pool)
 {
   merge_target_t *target;
-  const char *source_url;
-  svn_revnum_t source_peg_revnum;
+  repo_location_t *source_loc;
   apr_array_header_t *merge_sources;
-  url_uuid_t source_repos_root;
   svn_ra_session_t *ra_session;
   apr_pool_t *sesspool;
   svn_boolean_t use_sleep = FALSE;
@@ -10746,30 +10784,24 @@ merge_peg_locked(const char *source_path
 
   SVN_ERR_ASSERT(svn_dirent_is_absolute(target_abspath));
 
-  SVN_ERR(target_node_location(&target, target_abspath,
-                               ctx, scratch_pool, scratch_pool));
-  SVN_ERR(ensure_wc_is_suitable_merge_target(target_abspath, ctx,
-                                             allow_mixed_rev, TRUE, TRUE,
-                                             scratch_pool));
+  SVN_ERR(open_target_wc(&target, target_abspath,
+                         allow_mixed_rev, TRUE, TRUE,
+                         ctx, scratch_pool, scratch_pool));
 
   /* Open an RA session to our source URL, and determine its root URL. */
   sesspool = svn_pool_create(scratch_pool);
-  SVN_ERR(svn_client__ra_session_from_path(
-            &ra_session, &source_peg_revnum, &source_url,
-            source_path_or_url, NULL, source_peg_revision, source_peg_revision,
-            ctx, sesspool));
-  SVN_ERR(svn_ra_get_repos_root2(ra_session, &source_repos_root.url,
-                                 scratch_pool));
-  SVN_ERR(svn_ra_get_uuid2(ra_session, &source_repos_root.uuid, scratch_pool));
+  SVN_ERR(open_source_session(&source_loc, &ra_session,
+                              source_path_or_url, source_peg_revision,
+                              ctx, sesspool, scratch_pool));
 
   /* Normalize our merge sources. */
   SVN_ERR(normalize_merge_sources(&merge_sources, source_path_or_url,
-                                  source_url, source_peg_revnum,
+                                  source_loc->url, source_loc->rev,
                                   ranges_to_merge, ra_session, ctx,
                                   scratch_pool, scratch_pool));
 
   /* Check for same_repos. */
-  same_repos = is_same_repos(&target->repos_root, &source_repos_root,
+  same_repos = is_same_repos(target->loc.repo, source_loc->repo,
                              TRUE /* strict_urls */);
 
   /* We're done with our little RA session. */
@@ -10832,3 +10864,382 @@ svn_client_merge_peg4(const char *source
 
   return SVN_NO_ERROR;
 }
+
+#ifdef SVN_WITH_SYMMETRIC_MERGE
+
+/* Details of a symmetric merge. */
+struct svn_client__symmetric_merge_t
+{
+  repo_location_t *yca, *base, *mid, *right;
+};
+
+/* */
+typedef struct source_and_target_t
+{
+  repo_location_t *source;
+  svn_ra_session_t *source_ra_session;
+  merge_target_t *target;
+  svn_ra_session_t *target_ra_session;
+} source_and_target_t;
+
+/* "Open" the source and target branches of a merge.  That means:
+ *   - find out their exact repository locations (resolve WC paths and
+ *     non-numeric revision numbers),
+ *   - check the branches are suitably related,
+ *   - establish RA session(s) to the repo,
+ *   - check the WC for suitability (throw an error if unsuitable)
+ *
+ * Record this information and return it in a new "merge context" object.
+ */
+static svn_error_t *
+open_source_and_target(source_and_target_t **source_and_target,
+                       const char *source_path_or_url,
+                       const svn_opt_revision_t *source_peg_revision,
+                       const char *target_abspath,
+                       svn_boolean_t allow_mixed_rev,
+                       svn_client_ctx_t *ctx,
+                       apr_pool_t *session_pool,
+                       apr_pool_t *result_pool,
+                       apr_pool_t *scratch_pool)
+{
+  source_and_target_t *s_t = apr_palloc(result_pool, sizeof(*s_t));
+
+  /* Target */
+  SVN_ERR(open_target_wc(&s_t->target, target_abspath,
+                         allow_mixed_rev, TRUE, TRUE,
+                         ctx, result_pool, scratch_pool));
+  SVN_ERR(svn_client_open_ra_session(&s_t->target_ra_session,
+                                     s_t->target->loc.url,
+                                     ctx, session_pool));
+
+  /* Source */
+  SVN_ERR(open_source_session(&s_t->source, &s_t->source_ra_session,
+                              source_path_or_url, source_peg_revision,
+                              ctx, result_pool, scratch_pool));
+
+  *source_and_target = s_t;
+  return SVN_NO_ERROR;
+}
+
+/* "Close" any resources that were acquired in the S_T structure. */
+static svn_error_t *
+close_source_and_target(source_and_target_t *s_t,
+                        apr_pool_t *scratch_pool)
+{
+  /* close s_t->source_/target_ra_session */
+  return SVN_NO_ERROR;
+}
+
+/* Find a merge base location on the target branch, like in a sync
+ * merge.
+ *
+ *          (Source-left) (Source-right)
+ *                BASE        RIGHT
+ *          o-------o-----------o---
+ *         /         \           \
+ *   -----o     prev. \           \  this
+ *     YCA \    merge  \           \ merge
+ *          o-----------o-----------o
+ *                                TARGET
+ *
+ */
+static svn_error_t *
+find_base_on_source(repo_location_t **base_p,
+                    source_and_target_t *s_t,
+                    svn_client_ctx_t *ctx,
+                    apr_pool_t *result_pool,
+                    apr_pool_t *scratch_pool)
+{
+  *base_p = NULL;
+  return SVN_NO_ERROR;
+}
+
+/* Find a merge base location on the target branch, like in a reintegrate
+ * merge.
+ * 
+ *                     MID    RIGHT
+ *          o-----------o-------o---
+ *         /    prev.  /         \
+ *   -----o     merge /           \  this
+ *     YCA \         /             \ merge
+ *          o-------o---------------o
+ *                BASE            TARGET
+ *
+ * Set *BASE_P to the latest location on the history of S_T->target at
+ * which all revisions up to *BASE_P are recorded as merged into RIGHT
+ * (which is S_T->source).
+ * 
+ * ### TODO: Set *MID_P to the first location on the history of
+ * S_T->source at which all revisions up to BASE_P are recorded as merged.
+ */
+static svn_error_t *
+find_base_on_target(repo_location_t **base_p,
+                    repo_location_t **mid_p,
+                    source_and_target_t *s_t,
+                    svn_client_ctx_t *ctx,
+                    apr_pool_t *result_pool,
+                    apr_pool_t *scratch_pool)
+{
+  repo_location_t *base = apr_palloc(result_pool, sizeof(*base));
+  svn_mergeinfo_t unmerged_to_source_mergeinfo_catalog;
+  svn_mergeinfo_t merged_to_source_mergeinfo_catalog;
+  apr_hash_t *subtrees_with_mergeinfo;
+
+  /* Find all the subtrees in TARGET_WCPATH that have explicit mergeinfo. */
+  SVN_ERR(get_wc_explicit_mergeinfo_catalog(&subtrees_with_mergeinfo,
+                                            s_t->target->abspath,
+                                            svn_depth_infinity,
+                                            ctx, scratch_pool, scratch_pool));
+
+  SVN_ERR(calculate_left_hand_side(&base->url, &base->rev,
+                                   &merged_to_source_mergeinfo_catalog,
+                                   &unmerged_to_source_mergeinfo_catalog,
+                                   s_t->target,
+                                   subtrees_with_mergeinfo,
+                                   s_t->source,
+                                   s_t->source_ra_session,
+                                   s_t->target_ra_session,
+                                   ctx, result_pool, scratch_pool));
+
+  if (base->url)
+    {
+      *base_p = base;
+      *mid_p = s_t->source;  /* ### WRONG! This is quite difficult. */
+    }
+  else
+    {
+      *base_p = NULL;
+      *mid_p = NULL;
+    }
+
+  return SVN_NO_ERROR;
+}
+
+/* The body of svn_client__find_symmetric_merge(), which see.
+ */
+static svn_error_t *
+find_symmetric_merge(repo_location_t **yca_p,
+                     repo_location_t **base_p,
+                     repo_location_t **mid_p,
+                     source_and_target_t *s_t,
+                     svn_client_ctx_t *ctx,
+                     apr_pool_t *result_pool,
+                     apr_pool_t *scratch_pool)
+{
+  repo_location_t *yca, *base_on_source, *base_on_target, *mid;
+
+  yca = apr_palloc(result_pool, sizeof(*yca));
+  SVN_ERR(svn_client__get_youngest_common_ancestor(
+            NULL, &yca->url, &yca->rev,
+            s_t->source->url, s_t->source->rev,
+            s_t->target->loc.url, s_t->target->loc.rev,
+            ctx, result_pool));
+  *yca_p = yca;
+
+  /* Find the latest revision of A synced to B and the latest
+   * revision of B synced to A.
+   *
+   *   base_on_source = youngest_complete_synced_point(source, target)
+   *   base_on_target = youngest_complete_synced_point(target, source)
+   */
+  SVN_ERR(find_base_on_source(&base_on_source, s_t,
+                              ctx, scratch_pool, scratch_pool));
+  SVN_ERR(find_base_on_target(&base_on_target, &mid, s_t,
+                              ctx, scratch_pool, scratch_pool));
+
+  if (base_on_source)
+    SVN_DBG(("base on source: %s@%ld\n", base_on_source->url, base_on_source->rev));
+  if (base_on_target)
+    SVN_DBG(("base on target: %s@%ld\n", base_on_target->url, base_on_target->rev));
+
+  /* Choose a base. */
+  if (base_on_source
+      && (! base_on_target || (base_on_source->rev > base_on_target->rev)))
+    {
+      *base_p = base_on_source;
+      *mid_p = NULL;
+    }
+  else if (base_on_target)
+    {
+      *base_p = base_on_target;
+      *mid_p = mid;
+    }
+  else
+    {
+      /* No previous merge was found, so this is the simple case where
+       * the base is the youngest common ancestor of the branches.  We'll
+       * set MID=NULL; in theory the end result should be the same if we
+       * set MID=YCA instead. */
+      *base_p = yca;
+      *mid_p = NULL;
+    }
+
+  return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_client__find_symmetric_merge(svn_client__symmetric_merge_t **merge_p,
+                                 const char *source_path_or_url,
+                                 const svn_opt_revision_t *source_revision,
+                                 const char *target_wcpath,
+                                 svn_boolean_t allow_mixed_rev,
+                                 svn_client_ctx_t *ctx,
+                                 apr_pool_t *result_pool,
+                                 apr_pool_t *scratch_pool)
+{
+  const char *target_abspath;
+  source_and_target_t *s_t;
+  svn_client__symmetric_merge_t *merge = apr_palloc(result_pool, sizeof(*merge));
+
+  SVN_ERR(svn_dirent_get_absolute(&target_abspath, target_wcpath, scratch_pool));
+  SVN_ERR(open_source_and_target(&s_t, source_path_or_url, source_revision,
+                                 target_abspath, allow_mixed_rev,
+                                 ctx, result_pool, result_pool, scratch_pool));
+
+  /* Check source is in same repos as target. */
+  SVN_ERR(check_same_repos(s_t->source->repo, source_path_or_url,
+                           s_t->target->loc.repo, target_wcpath,
+                           TRUE /* strict_urls */, scratch_pool));
+
+  SVN_ERR(find_symmetric_merge(&merge->yca, &merge->base, &merge->mid, s_t,
+                               ctx, result_pool, scratch_pool));
+  merge->right = s_t->source;
+
+  *merge_p = merge;
+
+  SVN_ERR(close_source_and_target(s_t, scratch_pool));
+
+  return SVN_NO_ERROR;
+}
+
+/* The body of svn_client__do_symmetric_merge(), which see.
+ *
+ * Five locations are inputs: YCA, BASE, MID, RIGHT, TARGET, as shown
+ * depending on whether the base is on the source branch or the target
+ * branch of this merge.
+ *
+ *                     MID    RIGHT
+ *          o-----------o-------o---
+ *         /    prev.  /         \
+ *   -----o     merge /           \  this
+ *     YCA \         /             \ merge
+ *          o-------o---------------o
+ *                BASE            TARGET
+ *
+ * or
+ *
+ *                BASE        RIGHT      (and MID=NULL)
+ *          o-------o-----------o---
+ *         /         \           \
+ *   -----o     prev. \           \  this
+ *     YCA \    merge  \           \ merge
+ *          o-----------o-----------o
+ *                                TARGET
+ *
+ * ### TODO: The reintegrate-type (MID!=NULL) code path does not yet
+ * eliminate already-cherry-picked revisions from the source.
+ */
+static svn_error_t *
+do_symmetric_merge_locked(const svn_client__symmetric_merge_t *merge,
+                          const char *target_abspath,
+                          svn_depth_t depth,
+                          svn_boolean_t ignore_ancestry,
+                          svn_boolean_t force,
+                          svn_boolean_t record_only,
+                          svn_boolean_t dry_run,
+                          const apr_array_header_t *merge_options,
+                          svn_client_ctx_t *ctx,
+                          apr_pool_t *scratch_pool)
+{
+  merge_target_t *target;
+  merge_source_t *source = apr_palloc(scratch_pool, sizeof(*source));
+  svn_boolean_t use_sleep = FALSE;
+  svn_error_t *err;
+
+  SVN_ERR(open_target_wc(&target, target_abspath, TRUE, TRUE, TRUE,
+                         ctx, scratch_pool, scratch_pool));
+
+  source->url1 = merge->base->url;
+  source->rev1 = merge->base->rev;
+  source->url2 = merge->right->url;
+  source->rev2 = merge->right->rev;
+  SVN_DBG(("yca   %s@%ld\n", merge->yca->url, merge->yca->rev));
+  SVN_DBG(("base  %s@%ld\n", merge->base->url, merge->base->rev));
+  if (merge->mid)
+    SVN_DBG(("mid   %s@%ld\n", merge->mid->url, merge->mid->rev));
+  SVN_DBG(("right %s@%ld\n", merge->right->url, merge->right->rev));
+
+  if (merge->mid)
+    {
+      svn_ra_session_t *ra_session = NULL;
+
+      SVN_ERR(ensure_ra_session_url(&ra_session, source->url1,
+                                    ctx, scratch_pool));
+
+      err = merge_cousins_and_supplement_mergeinfo(target,
+                                                   ra_session, ra_session,
+                                                   source, merge->yca->rev,
+                                                   TRUE /* same_repos */,
+                                                   depth, ignore_ancestry,
+                                                   force, record_only,
+                                                   dry_run,
+                                                   merge_options, &use_sleep,
+                                                   ctx, scratch_pool);
+
+    }
+  else
+    {
+      apr_array_header_t *merge_sources;
+
+      merge_sources = apr_array_make(scratch_pool, 1, sizeof(merge_source_t *));
+      APR_ARRAY_PUSH(merge_sources, const merge_source_t *) = source;
+
+      err = do_merge(NULL, NULL, merge_sources, target,
+                     TRUE /*sources_ancestral*/, TRUE /*related*/,
+                     TRUE /*same_repos*/, ignore_ancestry, force, dry_run,
+                     record_only, NULL, FALSE, FALSE, depth, merge_options,
+                     &use_sleep, ctx, scratch_pool, scratch_pool);
+    }
+
+  if (use_sleep)
+    svn_io_sleep_for_timestamps(target_abspath, scratch_pool);
+
+  SVN_ERR(err);
+
+  return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_client__do_symmetric_merge(const svn_client__symmetric_merge_t *merge,
+                               const char *target_wcpath,
+                               svn_depth_t depth,
+                               svn_boolean_t ignore_ancestry,
+                               svn_boolean_t force,
+                               svn_boolean_t record_only,
+                               svn_boolean_t dry_run,
+                               const apr_array_header_t *merge_options,
+                               svn_client_ctx_t *ctx,
+                               apr_pool_t *pool)
+{
+  const char *target_abspath, *lock_abspath;
+
+  SVN_ERR(get_target_and_lock_abspath(&target_abspath, &lock_abspath,
+                                      target_wcpath, ctx, pool));
+
+  if (!dry_run)
+    SVN_WC__CALL_WITH_WRITE_LOCK(
+      do_symmetric_merge_locked(merge,
+                                target_abspath, depth, ignore_ancestry,
+                                force, record_only, dry_run,
+                                merge_options, ctx, pool),
+      ctx->wc_ctx, lock_abspath, FALSE /* lock_anchor */, pool);
+  else
+    SVN_ERR(do_symmetric_merge_locked(merge,
+                                target_abspath, depth, ignore_ancestry,
+                                force, record_only, dry_run,
+                                merge_options, ctx, pool));
+
+  return SVN_NO_ERROR;
+}
+
+#endif /* SVN_WITH_SYMMETRIC_MERGE */

Modified: subversion/branches/inheritable-props/subversion/libsvn_client/prop_commands.c
URL: http://svn.apache.org/viewvc/subversion/branches/inheritable-props/subversion/libsvn_client/prop_commands.c?rev=1303257&r1=1303256&r2=1303257&view=diff
==============================================================================
--- subversion/branches/inheritable-props/subversion/libsvn_client/prop_commands.c (original)
+++ subversion/branches/inheritable-props/subversion/libsvn_client/prop_commands.c Wed Mar 21 02:38:58 2012
@@ -75,7 +75,7 @@ is_revision_prop_name(const char *name)
 static svn_error_t *
 error_if_wcprop_name(const char *name)
 {
-  if (svn_property_kind(NULL, name) == svn_prop_wc_kind)
+  if (svn_property_kind2(name) == svn_prop_wc_kind)
     {
       return svn_error_createf
         (SVN_ERR_CLIENT_PROPERTY_NAME, NULL,
@@ -162,7 +162,7 @@ propset_on_url(const char *propname,
                svn_client_ctx_t *ctx,
                apr_pool_t *pool)
 {
-  enum svn_prop_kind prop_kind = svn_property_kind(NULL, propname);
+  enum svn_prop_kind prop_kind = svn_property_kind2(propname);
   svn_ra_session_t *ra_session;
   svn_node_kind_t node_kind;
   const char *message;
@@ -1039,7 +1039,7 @@ remote_proplist(const char *target_prefi
       svn_string_t *value = svn__apr_hash_index_val(hi);
       svn_prop_kind_t prop_kind;
 
-      prop_kind = svn_property_kind(NULL, name);
+      prop_kind = svn_property_kind2(name);
 
       if (prop_kind == svn_prop_regular_kind)
         {

Modified: subversion/branches/inheritable-props/subversion/libsvn_client/util.c
URL: http://svn.apache.org/viewvc/subversion/branches/inheritable-props/subversion/libsvn_client/util.c?rev=1303257&r1=1303256&r2=1303257&view=diff
==============================================================================
--- subversion/branches/inheritable-props/subversion/libsvn_client/util.c (original)
+++ subversion/branches/inheritable-props/subversion/libsvn_client/util.c Wed Mar 21 02:38:58 2012
@@ -90,10 +90,7 @@ svn_client__path_relative_to_root(const 
   /* If we have a WC path... */
   if (! svn_path_is_url(abspath_or_url))
     {
-      /* ...fetch its entry, and attempt to get both its full URL and
-         repository root URL.  If we can't get REPOS_ROOT from the WC
-         entry, we'll get it from the RA layer.*/
-
+      /* ... query it directly. */
       SVN_ERR(svn_wc__node_get_repos_relpath(&repos_relpath,
                                              wc_ctx,
                                              abspath_or_url,

Modified: subversion/branches/inheritable-props/subversion/libsvn_delta/compat.c
URL: http://svn.apache.org/viewvc/subversion/branches/inheritable-props/subversion/libsvn_delta/compat.c?rev=1303257&r1=1303256&r2=1303257&view=diff
==============================================================================
--- subversion/branches/inheritable-props/subversion/libsvn_delta/compat.c (original)
+++ subversion/branches/inheritable-props/subversion/libsvn_delta/compat.c Wed Mar 21 02:38:58 2012
@@ -31,7 +31,7 @@
 #include "svn_props.h"
 #include "svn_pools.h"
 
-
+
 struct file_rev_handler_wrapper_baton {
   void *baton;
   svn_file_rev_handler_old_t handler;
@@ -130,6 +130,8 @@ struct ev2_edit_baton
 {
   svn_editor_t *editor;
 
+  /* ### need to ensure we understand the proper root for these relpaths  */
+  apr_hash_t *changes;  /* RELPATH -> struct change_node  */
   apr_hash_t *paths;
   apr_array_header_t *path_order;
   int paths_processed;
@@ -179,7 +181,6 @@ enum action_code_t
   ACTION_ADD,
   ACTION_DELETE,
   ACTION_ADD_ABSENT,
-  ACTION_SET_TEXT,
   ACTION_UNLOCK
 };
 
@@ -189,26 +190,43 @@ struct path_action
   void *args;
 };
 
-struct prop_args
-{
-  const char *name;
-  svn_revnum_t base_revision;
-  const svn_string_t *value;
-  svn_kind_t kind;
-};
-
 struct copy_args
 {
   const char *copyfrom_path;
   svn_revnum_t copyfrom_rev;
 };
 
-struct path_checksum_args
+enum restructure_action_t
 {
-  const char *path;
+  RESTRUCTURE_NONE = 0,
+  RESTRUCTURE_ADD,         /* add the node, maybe replacing. maybe copy  */
+  RESTRUCTURE_ADD_ABSENT,  /* add an absent node, possibly replacing  */
+  RESTRUCTURE_DELETE,      /* delete this node  */
+};
+
+/* Records everything about how this node is to be changed.  */
+struct change_node
+{
+  /* what kind of (tree) restructure is occurring at this node?  */
+  enum restructure_action_t action;
+
+  svn_kind_t kind;  /* the NEW kind of this node  */
+
+  /* The revision we're trying to change. Replace it, modify it, etc.  */
   svn_revnum_t base_revision;
+
+  apr_hash_t *props;  /* new/final set of props to apply  */
+
+  const char *contents_abspath;  /* file containing new fulltext  */
+  svn_checksum_t *checksum;  /* checksum of new fulltext  */
+
+  /* If COPYFROM_PATH is not NULL, then copy PATH@REV to this node.
+     RESTRUCTURE must be RESTRUCTURE_ADD.  */
+  const char *copyfrom_path;
+  svn_revnum_t copyfrom_rev;
 };
 
+
 static svn_error_t *
 add_action(struct ev2_edit_baton *eb,
            const char *path,
@@ -238,6 +256,96 @@ add_action(struct ev2_edit_baton *eb,
   return SVN_NO_ERROR;
 }
 
+
+static struct change_node *
+locate_change(struct ev2_edit_baton *eb,
+              const char *relpath)
+{
+  struct change_node *change = apr_hash_get(eb->changes, relpath,
+                                            APR_HASH_KEY_STRING);
+  apr_array_header_t *action_list;
+
+  if (change != NULL)
+    return change;
+
+  /* Shift RELPATH into the proper pool.  */
+  relpath = apr_pstrdup(eb->edit_pool, relpath);
+
+  /* Investigate whether there is an action in PATHS. Any presence there
+     will determine whether we need to update PATH_ORDER.  */
+  action_list = apr_hash_get(eb->paths, relpath, APR_HASH_KEY_STRING);
+  if (action_list == NULL)
+    {
+      /* Store an empty ACTION_LIST into PATHS.  */
+      action_list = apr_array_make(eb->edit_pool, 1,
+                                   sizeof(struct path_action *));
+      apr_hash_set(eb->paths, relpath, APR_HASH_KEY_STRING, action_list);
+      APR_ARRAY_PUSH(eb->path_order, const char *) = relpath;
+    }
+
+  /* Return an empty change. Callers will tweak as needed.  */
+  change = apr_pcalloc(eb->edit_pool, sizeof(*change));
+  change->base_revision = SVN_INVALID_REVNUM;
+
+  apr_hash_set(eb->changes, relpath, APR_HASH_KEY_STRING, change);
+
+  return change;
+}
+
+
+static svn_error_t *
+apply_propedit(struct ev2_edit_baton *eb,
+               const char *relpath,
+               svn_kind_t kind,
+               svn_revnum_t base_revision,
+               const char *name,
+               const svn_string_t *value,
+               apr_pool_t *scratch_pool)
+{
+  struct change_node *change = locate_change(eb, relpath);
+
+  SVN_ERR_ASSERT(change->kind == svn_kind_unknown || change->kind == kind);
+  change->kind = kind;
+
+  SVN_ERR_ASSERT(!SVN_IS_VALID_REVNUM(change->base_revision)
+                 || change->base_revision == base_revision);
+  change->base_revision = base_revision;
+
+  if (change->props == NULL)
+    {
+      /* Fetch the original set of properties. We'll apply edits to create
+         the new/target set of properties.
+
+         If this is a copied/moved now, then the original properties come
+         from there. If the node has been added, it starts with empty props.
+         Otherwise, we get the properties from BASE.  */
+
+      if (change->copyfrom_path)
+        SVN_ERR(eb->fetch_props_func(&change->props,
+                                     eb->fetch_props_baton,
+                                     change->copyfrom_path,
+                                     change->copyfrom_rev,
+                                     eb->edit_pool, scratch_pool));
+      else if (change->action == RESTRUCTURE_ADD)
+        change->props = apr_hash_make(eb->edit_pool);
+      else
+        SVN_ERR(eb->fetch_props_func(&change->props,
+                                     eb->fetch_props_baton,
+                                     relpath, base_revision,
+                                     eb->edit_pool, scratch_pool));
+    }
+
+  if (value == NULL)
+    apr_hash_set(change->props, name, APR_HASH_KEY_STRING, NULL);
+  else
+    apr_hash_set(change->props,
+                 apr_pstrdup(eb->edit_pool, name), APR_HASH_KEY_STRING,
+                 svn_string_dup(value, eb->edit_pool));
+
+  return SVN_NO_ERROR;
+}
+
+
 /* Find all the paths which are immediate children of PATH and return their
    basenames in a list. */
 static apr_array_header_t *
@@ -275,12 +383,12 @@ get_children(struct ev2_edit_baton *eb,
 
 
 static svn_error_t *
-process_actions(void *edit_baton,
+process_actions(struct ev2_edit_baton *eb,
                 const char *path,
                 apr_array_header_t *actions,
+                const struct change_node *change,
                 apr_pool_t *scratch_pool)
 {
-  struct ev2_edit_baton *eb = edit_baton;
   apr_hash_t *props = NULL;
   svn_boolean_t need_add = FALSE;
   svn_boolean_t need_delete = FALSE;
@@ -311,42 +419,6 @@ process_actions(void *edit_baton,
 
       switch (action->action)
         {
-          case ACTION_PROPSET:
-            {
-              const struct prop_args *p_args = action->args;
-
-              kind = p_args->kind;
-
-              if (!SVN_IS_VALID_REVNUM(props_base_revision))
-                props_base_revision = p_args->base_revision;
-              else
-                SVN_ERR_ASSERT(p_args->base_revision == props_base_revision);
-
-              if (!props)
-                {
-                  /* Fetch the original props. We can then apply each of
-                     the modifications to it.  */
-                  if (need_delete && need_add)
-                    props = apr_hash_make(scratch_pool);
-                  else if (need_copy)
-                    SVN_ERR(eb->fetch_props_func(&props,
-                                                 eb->fetch_props_baton,
-                                                 copyfrom_path,
-                                                 copyfrom_rev,
-                                                 scratch_pool, scratch_pool));
-                  else
-                    SVN_ERR(eb->fetch_props_func(&props,
-                                                 eb->fetch_props_baton,
-                                                 path, props_base_revision,
-                                                 scratch_pool, scratch_pool));
-                }
-
-              /* Note that p_args->value may be NULL.  */
-              apr_hash_set(props, p_args->name, APR_HASH_KEY_STRING,
-                           p_args->value);
-              break;
-            }
-
           case ACTION_DELETE:
             {
               delete_revnum = *((svn_revnum_t *) action->args);
@@ -373,26 +445,6 @@ process_actions(void *edit_baton,
               break;
             }
 
-          case ACTION_SET_TEXT:
-            {
-              struct path_checksum_args *pca = action->args;
-
-              /* We can only set text on files. */
-              kind = svn_kind_file;
-
-              SVN_ERR(svn_io_file_checksum2(&checksum, pca->path,
-                                            svn_checksum_sha1, scratch_pool));
-              SVN_ERR(svn_stream_open_readonly(&contents, pca->path,
-                                               scratch_pool, scratch_pool));
-
-              if (!SVN_IS_VALID_REVNUM(text_base_revision))
-                text_base_revision = pca->base_revision;
-              else
-                SVN_ERR_ASSERT(pca->base_revision == text_base_revision);
-
-              break;
-            }
-
           case ACTION_COPY:
             {
               struct copy_args *c_args = action->args;
@@ -422,6 +474,32 @@ process_actions(void *edit_baton,
         }
     }
 
+  if (change != NULL)
+    {
+      if (change->contents_abspath != NULL)
+        {
+          /* We can only set text on files. */
+          /* ### validate we aren't overwriting KIND?  */
+          kind = svn_kind_file;
+
+          /* ### the checksum might be in CHANGE->CHECKSUM  */
+          SVN_ERR(svn_io_file_checksum2(&checksum, change->contents_abspath,
+                                        svn_checksum_sha1, scratch_pool));
+          SVN_ERR(svn_stream_open_readonly(&contents, change->contents_abspath,
+                                           scratch_pool, scratch_pool));
+
+          text_base_revision = change->base_revision;
+        }
+
+      if (change->props != NULL)
+        {
+          /* ### validate we aren't overwriting KIND?  */
+          kind = change->kind;
+          props = change->props;
+          props_base_revision = change->base_revision;
+        }
+    }
+
   /* We've now got a wholistic view of what has happened to this node,
    * so we can call our own editor APIs on it. */
 
@@ -486,10 +564,9 @@ process_actions(void *edit_baton,
 }
 
 static svn_error_t *
-run_ev2_actions(void *edit_baton,
+run_ev2_actions(struct ev2_edit_baton *eb,
                 apr_pool_t *scratch_pool)
 {
-  struct ev2_edit_baton *eb = edit_baton;
   apr_pool_t *iterpool;
 
   iterpool = svn_pool_create(scratch_pool);
@@ -502,9 +579,12 @@ run_ev2_actions(void *edit_baton,
                                        const char *);
       apr_array_header_t *actions = apr_hash_get(eb->paths, path,
                                                  APR_HASH_KEY_STRING);
+      struct change_node *change = apr_hash_get(eb->changes, path,
+                                                APR_HASH_KEY_STRING);
 
       svn_pool_clear(iterpool);
-      SVN_ERR(process_actions(edit_baton, path, actions, iterpool));
+
+      SVN_ERR(process_actions(eb, path, actions, change, iterpool));
     }
   svn_pool_destroy(iterpool);
 
@@ -554,10 +634,29 @@ ev2_delete_entry(const char *path,
 {
   struct ev2_dir_baton *pb = parent_baton;
   svn_revnum_t *revnum = apr_palloc(pb->eb->edit_pool, sizeof(*revnum));
+  struct change_node *change = locate_change(pb->eb, path);
+
+  if (SVN_IS_VALID_REVNUM(revision))
+    *revnum = revision;
+  else
+    *revnum = pb->base_revision;
 
-  *revnum = revision;
   SVN_ERR(add_action(pb->eb, path, ACTION_DELETE, revnum));
 
+  /* ### note: cannot switch to CHANGES just yet. the action loop needs
+     ### to see a delete action, and set NEED_DELETE. that is used for
+     ### the file properties. once fileprops are converted, then we
+     ### can fully switch over.  */
+
+  /* ### assert that RESTRUCTURE is NONE?  */
+  change->action = RESTRUCTURE_DELETE;
+
+#if 0
+  SVN_ERR_ASSERT(!SVN_IS_VALID_REVNUM(change->base_revision)
+                 || change->base_revision == revision);
+  change->base_revision = revision;
+#endif
+
   return SVN_NO_ERROR;
 }
 
@@ -571,6 +670,10 @@ ev2_add_directory(const char *path,
 {
   struct ev2_dir_baton *pb = parent_baton;
   struct ev2_dir_baton *cb = apr_pcalloc(result_pool, sizeof(*cb));
+  struct change_node *change = locate_change(pb->eb, path);
+
+  /* ### assert that RESTRUCTURE is NONE or DELETE?  */
+  change->action = RESTRUCTURE_ADD;
 
   cb->eb = pb->eb;
   cb->path = apr_pstrdup(result_pool, path);
@@ -604,6 +707,9 @@ ev2_add_directory(const char *path,
 
       cb->copyfrom_path = args->copyfrom_path;
       cb->copyfrom_rev = args->copyfrom_rev;
+
+      change->copyfrom_path = apr_pstrdup(pb->eb->edit_pool, copyfrom_path);
+      change->copyfrom_rev = copyfrom_revision;
     }
 
   return SVN_NO_ERROR;
@@ -644,14 +750,9 @@ ev2_change_dir_prop(void *dir_baton,
                     apr_pool_t *scratch_pool)
 {
   struct ev2_dir_baton *db = dir_baton;
-  struct prop_args *p_args = apr_palloc(db->eb->edit_pool, sizeof(*p_args));
-
-  p_args->name = apr_pstrdup(db->eb->edit_pool, name);
-  p_args->value = value ? svn_string_dup(value, db->eb->edit_pool) : NULL;
-  p_args->base_revision = db->base_revision;
-  p_args->kind = svn_kind_dir;
 
-  SVN_ERR(add_action(db->eb, db->path, ACTION_PROPSET, p_args));
+  SVN_ERR(apply_propedit(db->eb, db->path, svn_kind_dir, db->base_revision,
+                         name, value, scratch_pool));
 
   return SVN_NO_ERROR;
 }
@@ -687,6 +788,10 @@ ev2_add_file(const char *path,
 {
   struct ev2_file_baton *fb = apr_pcalloc(result_pool, sizeof(*fb));
   struct ev2_dir_baton *pb = parent_baton;
+  struct change_node *change = locate_change(pb->eb, path);
+
+  /* ### assert that RESTRUCTURE is NONE or DELETE?  */
+  change->action = RESTRUCTURE_ADD;
 
   fb->eb = pb->eb;
   fb->path = apr_pstrdup(result_pool, path);
@@ -717,6 +822,9 @@ ev2_add_file(const char *path,
       args->copyfrom_path = apr_pstrdup(pb->eb->edit_pool, copyfrom_path);
       args->copyfrom_rev = copyfrom_revision;
       SVN_ERR(add_action(pb->eb, path, ACTION_COPY, args));
+
+      change->copyfrom_path = apr_pstrdup(fb->eb->edit_pool, copyfrom_path);
+      change->copyfrom_rev = copyfrom_revision;
     }
 
   return SVN_NO_ERROR;
@@ -799,11 +907,14 @@ ev2_apply_textdelta(void *file_baton,
   struct ev2_file_baton *fb = file_baton;
   apr_pool_t *handler_pool = svn_pool_create(fb->eb->edit_pool);
   struct handler_baton *hb = apr_pcalloc(handler_pool, sizeof(*hb));
+  struct change_node *change;
   svn_stream_t *target;
-  struct path_checksum_args *pca = apr_pcalloc(fb->eb->edit_pool,
-                                               sizeof(*pca));
 
-  pca->base_revision = fb->base_revision;
+  change = locate_change(fb->eb, fb->path);
+  SVN_ERR_ASSERT(change->contents_abspath == NULL);
+  SVN_ERR_ASSERT(!SVN_IS_VALID_REVNUM(change->base_revision)
+                 || change->base_revision == fb->base_revision);
+  change->base_revision = fb->base_revision;
 
   if (! fb->delta_base)
     hb->source = svn_stream_empty(handler_pool);
@@ -811,7 +922,7 @@ ev2_apply_textdelta(void *file_baton,
     SVN_ERR(svn_stream_open_readonly(&hb->source, fb->delta_base, handler_pool,
                                      result_pool));
 
-  SVN_ERR(svn_stream_open_unique(&target, &pca->path, NULL,
+  SVN_ERR(svn_stream_open_unique(&target, &change->contents_abspath, NULL,
                                  svn_io_file_del_on_pool_cleanup,
                                  fb->eb->edit_pool, result_pool));
 
@@ -825,8 +936,6 @@ ev2_apply_textdelta(void *file_baton,
   *handler_baton = hb;
   *handler = window_handler;
 
-  SVN_ERR(add_action(fb->eb, fb->path, ACTION_SET_TEXT, pca));
-
   return SVN_NO_ERROR;
 }
 
@@ -837,7 +946,6 @@ ev2_change_file_prop(void *file_baton,
                      apr_pool_t *scratch_pool)
 {
   struct ev2_file_baton *fb = file_baton;
-  struct prop_args *p_args = apr_palloc(fb->eb->edit_pool, sizeof(*p_args));
 
   if (!strcmp(name, SVN_PROP_ENTRY_LOCK_TOKEN) && value == NULL)
     {
@@ -846,14 +954,8 @@ ev2_change_file_prop(void *file_baton,
       SVN_ERR(add_action(fb->eb, fb->path, ACTION_UNLOCK, NULL));
     }
 
-  /* We also pass through the deletion, since there may actually exist such
-     a property we want to get rid of.   In the worse case, this is a no-op. */
-  p_args->name = apr_pstrdup(fb->eb->edit_pool, name);
-  p_args->value = value ? svn_string_dup(value, fb->eb->edit_pool) : NULL;
-  p_args->base_revision = fb->base_revision;
-  p_args->kind = svn_kind_file;
-
-  SVN_ERR(add_action(fb->eb, fb->path, ACTION_PROPSET, p_args));
+  SVN_ERR(apply_propedit(fb->eb, fb->path, svn_kind_file, fb->base_revision,
+                         name, value, scratch_pool));
 
   return SVN_NO_ERROR;
 }
@@ -960,6 +1062,7 @@ delta_from_editor(const svn_delta_editor
   struct ev2_edit_baton *eb = apr_pcalloc(pool, sizeof(*eb));
 
   eb->editor = editor;
+  eb->changes = apr_hash_make(pool);
   eb->paths = apr_hash_make(pool);
   eb->path_order = apr_array_make(pool, 1, sizeof(const char *));
   eb->edit_pool = pool;
@@ -982,10 +1085,9 @@ delta_from_editor(const svn_delta_editor
   return SVN_NO_ERROR;
 }
 
-
-
-
 
+/* ### note the similarity to struct change_node. these structures will
+   ### be combined in the future.  */
 struct operation {
   enum {
     OP_OPEN,