You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@subversion.apache.org by hw...@apache.org on 2011/12/19 19:49:43 UTC

svn commit: r1220893 [6/19] - in /subversion/branches/fs-py: ./ build/ build/ac-macros/ build/generator/ build/generator/templates/ build/win32/ contrib/client-side/emacs/ contrib/server-side/mod_dontdothat/ notes/ subversion/bindings/javahl/native/ su...

Modified: subversion/branches/fs-py/subversion/libsvn_client/mergeinfo.c
URL: http://svn.apache.org/viewvc/subversion/branches/fs-py/subversion/libsvn_client/mergeinfo.c?rev=1220893&r1=1220892&r2=1220893&view=diff
==============================================================================
--- subversion/branches/fs-py/subversion/libsvn_client/mergeinfo.c (original)
+++ subversion/branches/fs-py/subversion/libsvn_client/mergeinfo.c Mon Dec 19 18:49:34 2011
@@ -38,6 +38,7 @@
 #include "svn_client.h"
 #include "svn_hash.h"
 
+#include "private/svn_opt_private.h"
 #include "private/svn_mergeinfo_private.h"
 #include "private/svn_wc_private.h"
 #include "private/svn_ra_private.h"
@@ -67,6 +68,16 @@ svn_client__merge_path_dup(const svn_cli
   return new;
 }
 
+svn_client__merge_path_t *
+svn_client__merge_path_create(const char *abspath,
+                              apr_pool_t *pool)
+{
+  svn_client__merge_path_t *result = apr_pcalloc(pool, sizeof(*result));
+
+  result->abspath = apr_pstrdup(pool, abspath);
+  return result;
+}
+
 svn_error_t *
 svn_client__parse_mergeinfo(svn_mergeinfo_t *mergeinfo,
                             svn_wc_context_t *wc_ctx,
@@ -136,42 +147,56 @@ svn_client__record_wc_mergeinfo(const ch
   return SVN_NO_ERROR;
 }
 
-/*-----------------------------------------------------------------------*/
-
-/*** Retrieving mergeinfo. ***/
-
 svn_error_t *
-svn_client__adjust_mergeinfo_source_paths(svn_mergeinfo_t adjusted_mergeinfo,
-                                          const char *rel_path,
-                                          svn_mergeinfo_t mergeinfo,
-                                          apr_pool_t *pool)
+svn_client__record_wc_mergeinfo_catalog(apr_hash_t *result_catalog,
+                                        svn_client_ctx_t *ctx,
+                                        apr_pool_t *scratch_pool)
 {
-  apr_hash_index_t *hi;
-  const char *path;
-  apr_array_header_t *copied_rangelist;
-
-  SVN_ERR_ASSERT(adjusted_mergeinfo);
-  SVN_ERR_ASSERT(mergeinfo);
+  apr_pool_t *iterpool = svn_pool_create(scratch_pool);
 
-  for (hi = apr_hash_first(pool, mergeinfo); hi; hi = apr_hash_next(hi))
+  if (apr_hash_count(result_catalog))
     {
-      const char *merge_source = svn__apr_hash_index_key(hi);
-      apr_array_header_t *rangelist = svn__apr_hash_index_val(hi);
-
-      /* Copy inherited mergeinfo into our output hash, adjusting the
-         merge source as appropriate. */
-      path = svn_fspath__join(merge_source, rel_path, pool);
-      copied_rangelist = svn_rangelist_dup(rangelist, pool);
-      apr_hash_set(adjusted_mergeinfo, path, APR_HASH_KEY_STRING,
-                   copied_rangelist);
+      int i;
+      apr_array_header_t *sorted_cat =
+        svn_sort__hash(result_catalog, svn_sort_compare_items_as_paths,
+                       scratch_pool);
+      for (i = 0; i < sorted_cat->nelts; i++)
+        {
+          svn_sort__item_t elt = APR_ARRAY_IDX(sorted_cat, i,
+                                               svn_sort__item_t);
+          svn_error_t *err;
+
+          svn_pool_clear(iterpool);
+          err = svn_client__record_wc_mergeinfo(elt.key, elt.value, TRUE,
+                                                ctx, iterpool);
+
+          if (err && err->apr_err == SVN_ERR_ENTRY_NOT_FOUND)
+            {
+              /* PATH isn't just missing, it's not even versioned as far
+                 as this working copy knows.  But it was included in
+                 MERGES, which means that the server knows about it.
+                 Likely we don't have access to the source due to authz
+                 restrictions.  For now just clear the error and
+                 continue... */
+              svn_error_clear(err);
+            }
+          else
+            {
+              SVN_ERR(err);
+            }
+        }
     }
+  svn_pool_destroy(iterpool);
   return SVN_NO_ERROR;
 }
 
+/*-----------------------------------------------------------------------*/
+
+/*** Retrieving mergeinfo. ***/
 
 svn_error_t *
 svn_client__get_wc_mergeinfo(svn_mergeinfo_t *mergeinfo,
-                             svn_boolean_t *inherited,
+                             svn_boolean_t *inherited_p,
                              svn_mergeinfo_inheritance_t inherit,
                              const char *local_abspath,
                              const char *limit_abspath,
@@ -185,6 +210,7 @@ svn_client__get_wc_mergeinfo(svn_mergein
   svn_mergeinfo_t wc_mergeinfo;
   svn_revnum_t base_revision;
   apr_pool_t *iterpool;
+  svn_boolean_t inherited;
 
   SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath));
   if (limit_abspath)
@@ -287,7 +313,7 @@ svn_client__get_wc_mergeinfo(svn_mergein
   if (svn_path_is_empty(walk_relpath))
     {
       /* Mergeinfo is explicit. */
-      *inherited = FALSE;
+      inherited = FALSE;
       *mergeinfo = wc_mergeinfo;
     }
   else
@@ -295,16 +321,16 @@ svn_client__get_wc_mergeinfo(svn_mergein
       /* Mergeinfo may be inherited. */
       if (wc_mergeinfo)
         {
-          *inherited = TRUE;
-          *mergeinfo = apr_hash_make(result_pool);
-          SVN_ERR(svn_client__adjust_mergeinfo_source_paths(*mergeinfo,
-                                                            walk_relpath,
-                                                            wc_mergeinfo,
-                                                            result_pool));
+          inherited = TRUE;
+          SVN_ERR(svn_mergeinfo__add_suffix_to_mergeinfo(mergeinfo,
+                                                         wc_mergeinfo,
+                                                         walk_relpath,
+                                                         result_pool,
+                                                         scratch_pool));
         }
       else
         {
-          *inherited = FALSE;
+          inherited = FALSE;
           *mergeinfo = NULL;
         }
     }
@@ -314,7 +340,7 @@ svn_client__get_wc_mergeinfo(svn_mergein
 
   /* Remove non-inheritable mergeinfo and paths mapped to empty ranges
      which may occur if WCPATH's mergeinfo is not explicit. */
-  if (*inherited
+  if (inherited
       && apr_hash_count(*mergeinfo)) /* Nothing to do for empty mergeinfo. */
     {
       SVN_ERR(svn_mergeinfo_inheritable2(mergeinfo, *mergeinfo, NULL,
@@ -323,6 +349,9 @@ svn_client__get_wc_mergeinfo(svn_mergein
       svn_mergeinfo__remove_empty_rangelists(*mergeinfo, result_pool);
     }
 
+  if (inherited_p)
+    *inherited_p = inherited;
+
   return SVN_NO_ERROR;
 }
 
@@ -353,14 +382,15 @@ svn_client__get_wc_mergeinfo_catalog(svn
     {
       if (walked_path)
         *walked_path = "";
-      *inherited = FALSE;
+      if (inherited)
+        *inherited = FALSE;
       return SVN_NO_ERROR;
     }
 
   SVN_ERR(svn_client__path_relative_to_root(&target_repos_rel_path,
                                             ctx->wc_ctx,
                                             local_abspath,
-                                            repos_root, FALSE,
+                                            NULL, FALSE,
                                             NULL, scratch_pool,
                                             scratch_pool));
 
@@ -411,7 +441,7 @@ svn_client__get_wc_mergeinfo_catalog(svn
 
           SVN_ERR(svn_client__path_relative_to_root(&key_path, ctx->wc_ctx,
                                                     key_path,
-                                                    repos_root, FALSE,
+                                                    NULL, FALSE,
                                                     NULL, result_pool,
                                                     scratch_pool));
           SVN_ERR(svn_mergeinfo_parse(&subtree_mergeinfo, propval->data,
@@ -432,8 +462,8 @@ svn_client__get_wc_mergeinfo_catalog(svn
 }
 
 svn_error_t *
-svn_client__get_repos_mergeinfo(svn_ra_session_t *ra_session,
-                                svn_mergeinfo_t *target_mergeinfo,
+svn_client__get_repos_mergeinfo(svn_mergeinfo_t *target_mergeinfo,
+                                svn_ra_session_t *ra_session,
                                 const char *rel_path,
                                 svn_revnum_t rev,
                                 svn_mergeinfo_inheritance_t inherit,
@@ -490,6 +520,7 @@ svn_client__get_repos_mergeinfo_catalog(
         {
           svn_error_clear(err);
           *mergeinfo_cat = NULL;
+          return SVN_NO_ERROR;
         }
       else
         return svn_error_trace(err);
@@ -501,38 +532,21 @@ svn_client__get_repos_mergeinfo_catalog(
     }
   else
     {
-      const char *repos_root;
+      const char *session_relpath;
       const char *session_url;
 
-      SVN_ERR(svn_ra_get_repos_root2(ra_session, &repos_root, scratch_pool));
       SVN_ERR(svn_ra_get_session_url(ra_session, &session_url, scratch_pool));
+      SVN_ERR(svn_ra_get_path_relative_to_root(ra_session, &session_relpath,
+                                               session_url, scratch_pool));
 
-      if (strcmp(repos_root, session_url) == 0)
-        {
-          *mergeinfo_cat = repos_mergeinfo_cat;
-        }
+      if (session_relpath[0] == '\0')
+        *mergeinfo_cat = repos_mergeinfo_cat;
       else
-        {
-          apr_hash_index_t *hi;
-          svn_mergeinfo_catalog_t rekeyed_mergeinfo_cat =
-            apr_hash_make(result_pool);
-
-          for (hi = apr_hash_first(scratch_pool, repos_mergeinfo_cat);
-               hi;
-               hi = apr_hash_next(hi))
-            {
-              const char *path =
-                svn_path_url_add_component2(session_url,
-                                            svn__apr_hash_index_key(hi),
-                                            scratch_pool);
-              SVN_ERR(svn_ra_get_path_relative_to_root(ra_session, &path,
-                                                       path,
-                                                       result_pool));
-              apr_hash_set(rekeyed_mergeinfo_cat, path, APR_HASH_KEY_STRING,
-                           svn__apr_hash_index_val(hi));
-            }
-          *mergeinfo_cat = rekeyed_mergeinfo_cat;
-        }
+        SVN_ERR(svn_mergeinfo__add_prefix_to_catalog(mergeinfo_cat,
+                                                     repos_mergeinfo_cat,
+                                                     session_relpath,
+                                                     result_pool,
+                                                     scratch_pool));
     }
   return SVN_NO_ERROR;
 }
@@ -579,7 +593,7 @@ svn_client__get_wc_or_repos_mergeinfo(sv
 svn_error_t *
 svn_client__get_wc_or_repos_mergeinfo_catalog(
   svn_mergeinfo_catalog_t *target_mergeinfo_catalog,
-  svn_boolean_t *inherited,
+  svn_boolean_t *inherited_p,
   svn_boolean_t *from_repos,
   svn_boolean_t include_descendants,
   svn_boolean_t repos_only,
@@ -621,8 +635,9 @@ svn_client__get_wc_or_repos_mergeinfo_ca
 
   if (!repos_only)
     {
+      svn_boolean_t inherited;
       SVN_ERR(svn_client__get_wc_mergeinfo_catalog(&target_mergeinfo_cat_wc,
-                                                   inherited,
+                                                   &inherited,
                                                    include_descendants,
                                                    inherit,
                                                    local_abspath,
@@ -631,11 +646,13 @@ svn_client__get_wc_or_repos_mergeinfo_ca
                                                    ctx,
                                                    result_pool,
                                                    scratch_pool));
+      if (inherited_p)
+        *inherited_p = inherited;
 
       /* If we want LOCAL_ABSPATH's inherited mergeinfo, were we able to
          get it from the working copy?  If not, then we must ask the
          repository. */
-      if (! ((*inherited)
+      if (! (inherited
              || (inherit == svn_mergeinfo_explicit)
              || (repos_relpath
                  && target_mergeinfo_cat_wc
@@ -693,7 +710,8 @@ svn_client__get_wc_or_repos_mergeinfo_ca
                                   repos_relpath,
                                   APR_HASH_KEY_STRING))
                 {
-                  *inherited = TRUE;
+                  if (inherited_p)
+                    *inherited_p = TRUE;
                   if (from_repos)
                     *from_repos = TRUE;
                 }
@@ -801,7 +819,7 @@ should_elide_mergeinfo(svn_boolean_t *el
                        svn_mergeinfo_t parent_mergeinfo,
                        svn_mergeinfo_t child_mergeinfo,
                        const char *path_suffix,
-                       apr_pool_t *pool)
+                       apr_pool_t *scratch_pool)
 {
   /* Easy out: No child mergeinfo to elide. */
   if (child_mergeinfo == NULL)
@@ -826,22 +844,18 @@ should_elide_mergeinfo(svn_boolean_t *el
       /* Both CHILD_MERGEINFO and PARENT_MERGEINFO are non-NULL and
          non-empty. */
       svn_mergeinfo_t path_tweaked_parent_mergeinfo;
-      apr_pool_t *subpool = svn_pool_create(pool);
-
-      path_tweaked_parent_mergeinfo = apr_hash_make(subpool);
 
       /* If we need to adjust the paths in PARENT_MERGEINFO do it now. */
       if (path_suffix)
-        SVN_ERR(svn_client__adjust_mergeinfo_source_paths(
-          path_tweaked_parent_mergeinfo,
-          path_suffix, parent_mergeinfo, subpool));
+        SVN_ERR(svn_mergeinfo__add_suffix_to_mergeinfo(
+                  &path_tweaked_parent_mergeinfo, parent_mergeinfo,
+                  path_suffix, scratch_pool, scratch_pool));
       else
         path_tweaked_parent_mergeinfo = parent_mergeinfo;
 
       SVN_ERR(svn_mergeinfo__equals(elides,
                                     path_tweaked_parent_mergeinfo,
-                                    child_mergeinfo, TRUE, subpool));
-      svn_pool_destroy(subpool);
+                                    child_mergeinfo, TRUE, scratch_pool));
     }
 
   return SVN_NO_ERROR;
@@ -958,7 +972,7 @@ svn_client__elide_mergeinfo(const char *
         return SVN_NO_ERROR;
 
       /* Get TARGET_WCPATH's inherited mergeinfo from the WC. */
-      err = svn_client__get_wc_mergeinfo(&mergeinfo, &inherited,
+      err = svn_client__get_wc_mergeinfo(&mergeinfo, NULL,
                                          svn_mergeinfo_nearest_ancestor,
                                          target_abspath,
                                          limit_abspath,
@@ -983,7 +997,7 @@ svn_client__elide_mergeinfo(const char *
       if (!mergeinfo && !wc_elision_limit_path)
         {
           err = svn_client__get_wc_or_repos_mergeinfo(
-            &mergeinfo, &inherited, NULL, TRUE,
+            &mergeinfo, NULL, NULL, TRUE,
             svn_mergeinfo_nearest_ancestor,
             NULL, target_wcpath, ctx, pool);
           if (err)
@@ -1047,12 +1061,10 @@ get_mergeinfo(svn_mergeinfo_catalog_t *m
   const char *url;
   svn_boolean_t use_url = svn_path_is_url(path_or_url);
   svn_revnum_t peg_rev;
-  svn_opt_revision_t opt_rev;
-  opt_rev.kind = svn_opt_revision_unspecified;
 
   SVN_ERR(svn_client__ra_session_from_path(&ra_session, &peg_rev, &url,
                                            path_or_url, NULL, peg_revision,
-                                           &opt_rev, ctx, scratch_pool));
+                                           peg_revision, ctx, scratch_pool));
 
   /* If PATH_OR_URL is as working copy path determine if we will need to
      contact the repository for the requested PEG_REVISION. */
@@ -1097,11 +1109,9 @@ get_mergeinfo(svn_mergeinfo_catalog_t *m
     }
   else /* ! svn_path_is_url() */
     {
-      svn_boolean_t inherited;
-
       /* Acquire return values. */
       SVN_ERR(svn_client__get_wc_or_repos_mergeinfo_catalog(
-        mergeinfo_catalog, &inherited, NULL, include_descendants, FALSE,
+        mergeinfo_catalog, NULL, NULL, include_descendants, FALSE,
         ignore_invalid_mergeinfo, svn_mergeinfo_inherited,
         ra_session, path_or_url, ctx,
         result_pool, scratch_pool));
@@ -1111,141 +1121,74 @@ get_mergeinfo(svn_mergeinfo_catalog_t *m
 }
 
 /*** In-memory mergeinfo elision ***/
-
-/* TODO(reint): Document. */
-struct elide_mergeinfo_catalog_dir_baton {
-  const char *inherited_mergeinfo_path;
-  svn_mergeinfo_t mergeinfo_catalog;
-};
-
-/* The root doesn't have mergeinfo (unless it is actually one of the
-   paths passed to svn_delta_path_driver, in which case the callback
-   is called directly instead of this). */
-static svn_error_t *
-elide_mergeinfo_catalog_open_root(void *eb,
-                                  svn_revnum_t base_revision,
-                                  apr_pool_t *dir_pool,
-                                  void **root_baton)
-{
-  struct elide_mergeinfo_catalog_dir_baton *b = apr_pcalloc(dir_pool,
-                                                            sizeof(*b));
-  b->mergeinfo_catalog = eb;
-  *root_baton = b;
-  return SVN_NO_ERROR;
-}
-
-/* Make a directory baton for PATH.  It should have the same
-   inherited_mergeinfo_path as its parent... unless we just called
-   elide_mergeinfo_catalog_cb on its parent with its path. */
-static svn_error_t *
-elide_mergeinfo_catalog_open_directory(const char *path,
-                                       void *parent_baton,
-                                       svn_revnum_t base_revision,
-                                       apr_pool_t *dir_pool,
-                                       void **child_baton)
-{
-  struct elide_mergeinfo_catalog_dir_baton *b, *pb = parent_baton;
-
-  b = apr_pcalloc(dir_pool, sizeof(*b));
-  b->mergeinfo_catalog = pb->mergeinfo_catalog;
-
-  if (apr_hash_get(b->mergeinfo_catalog, path, APR_HASH_KEY_STRING))
-    b->inherited_mergeinfo_path = apr_pstrdup(dir_pool, path);
-  else
-    b->inherited_mergeinfo_path = pb->inherited_mergeinfo_path;
-
-  *child_baton = b;
-  return SVN_NO_ERROR;
-}
-
-/* TODO(reint): Document. */
-struct elide_mergeinfo_catalog_cb_baton {
-  apr_array_header_t *elidable_paths;
-  svn_mergeinfo_t mergeinfo_catalog;
-  apr_pool_t *result_pool;
-};
-
-/* Implements svn_delta_path_driver_cb_func_t. */
-static svn_error_t *
-elide_mergeinfo_catalog_cb(void **dir_baton,
-                           void *parent_baton,
-                           void *callback_baton,
-                           const char *path,
-                           apr_pool_t *pool)
+svn_error_t *
+svn_client__elide_mergeinfo_catalog(svn_mergeinfo_catalog_t mergeinfo_catalog,
+                                    apr_pool_t *scratch_pool)
 {
-  struct elide_mergeinfo_catalog_cb_baton *cb = callback_baton;
-  struct elide_mergeinfo_catalog_dir_baton *pb = parent_baton;
-  const char *path_suffix;
-  svn_boolean_t elides;
-
-  /* pb == NULL would imply that there was an *empty* path in the
-     paths given to the driver (which is different from "/"). */
-  SVN_ERR_ASSERT(pb != NULL);
-
-  /* We'll just act like everything is a file. */
-  *dir_baton = NULL;
-
-  /* Is there even any inherited mergeinfo to elide? */
-  /* (Note that svn_delta_path_driver will call open_directory before
-     the callback for the root (only).) */
-  if (!pb->inherited_mergeinfo_path
-      || strcmp(path, "/") == 0)
-    return SVN_NO_ERROR;
+  apr_array_header_t *sorted_hash;
+  apr_array_header_t *elidable_paths = apr_array_make(scratch_pool, 1,
+                                                      sizeof(const char *));
+  apr_array_header_t *dir_stack = apr_array_make(scratch_pool, 1,
+                                                 sizeof(const char *));
+  apr_pool_t *iterpool;
+  int i;
 
-  path_suffix = svn_dirent_is_child(pb->inherited_mergeinfo_path,
-                                    path, NULL);
-  SVN_ERR_ASSERT(path_suffix != NULL);
+  /* Here's the general algorithm:
+     Walk through the paths sorted in tree order.  For each path, pop
+     the dir_stack until it is either empty or the top item contains a parent
+     of the current path. Check to see if that mergeinfo is then elidable,
+     and build the list of elidable mergeinfo based upon that determination.
+     Finally, push the path of interest onto the stack, and continue. */
+  sorted_hash = svn_sort__hash(mergeinfo_catalog,
+                               svn_sort_compare_items_as_paths,
+                               scratch_pool);
+  iterpool = svn_pool_create(scratch_pool);
+  for (i = 0; i < sorted_hash->nelts; i++)
+    {
+      svn_sort__item_t *item = &APR_ARRAY_IDX(sorted_hash, i,
+                                              svn_sort__item_t);
+      const char *path = item->key;
+
+      if (dir_stack->nelts > 0)
+        {
+          const char *top;
+          const char *path_suffix;
+          svn_boolean_t elides = FALSE;
+          
+          svn_pool_clear(iterpool);
 
-  SVN_ERR(should_elide_mergeinfo(&elides,
-                                 apr_hash_get(cb->mergeinfo_catalog,
-                                              pb->inherited_mergeinfo_path,
-                                              APR_HASH_KEY_STRING),
-                                 apr_hash_get(cb->mergeinfo_catalog,
-                                              path,
-                                              APR_HASH_KEY_STRING),
-                                 path_suffix,
-                                 pool));
+          /* Pop off any paths which are not ancestors of PATH. */
+          do
+            {
+              top = APR_ARRAY_IDX(dir_stack, dir_stack->nelts - 1,
+                                          const char *);
+              path_suffix = svn_dirent_is_child(top, path, NULL);
 
-  if (elides)
-    APR_ARRAY_PUSH(cb->elidable_paths, const char *) =
-      apr_pstrdup(cb->result_pool, path);
+              if (!path_suffix)
+                apr_array_pop(dir_stack);
+            }
+          while (dir_stack->nelts > 0 && !path_suffix);
 
-  return SVN_NO_ERROR;
-}
+          /* If we have a path suffix, it means we haven't popped the stack
+             clean. */
+          if (path_suffix)
+            {
+              SVN_ERR(should_elide_mergeinfo(&elides,
+                                         apr_hash_get(mergeinfo_catalog, top,
+                                                      APR_HASH_KEY_STRING),
+                                         apr_hash_get(mergeinfo_catalog, path,
+                                                      APR_HASH_KEY_STRING),
+                                         path_suffix,
+                                         iterpool));
 
-svn_error_t *
-svn_client__elide_mergeinfo_catalog(svn_mergeinfo_t mergeinfo_catalog,
-                                    apr_pool_t *pool)
-{
-  apr_array_header_t *paths;
-  apr_array_header_t *elidable_paths = apr_array_make(pool, 1,
-                                                      sizeof(const char *));
-  svn_delta_editor_t *editor = svn_delta_default_editor(pool);
-  struct elide_mergeinfo_catalog_cb_baton cb = { 0 };
-  void *eb;
-  int i;
+              if (elides)
+                APR_ARRAY_PUSH(elidable_paths, const char *) = path;
+            }
+        }
 
-  cb.elidable_paths = elidable_paths;
-  cb.mergeinfo_catalog = mergeinfo_catalog;
-  cb.result_pool = pool;
-
-  editor->open_root = elide_mergeinfo_catalog_open_root;
-  editor->open_directory = elide_mergeinfo_catalog_open_directory;
-
-  eb = mergeinfo_catalog;
-  SVN_ERR(svn_editor__insert_shims((const svn_delta_editor_t **)&editor, &eb,
-                                   editor, eb, NULL, NULL, NULL, NULL,
-                                   pool, pool));
-
-  /* Walk over the paths, and build up a list of elidable ones. */
-  SVN_ERR(svn_hash_keys(&paths, mergeinfo_catalog, pool));
-  SVN_ERR(svn_delta_path_driver(editor,
-                                eb,
-                                SVN_INVALID_REVNUM,
-                                paths,
-                                elide_mergeinfo_catalog_cb,
-                                &cb,
-                                pool));
+      APR_ARRAY_PUSH(dir_stack, const char *) = path;
+    }
+  svn_pool_destroy(iterpool);
 
   /* Now remove the elidable paths from the catalog. */
   for (i = 0; i < elidable_paths->nelts; i++)
@@ -1265,8 +1208,8 @@ svn_client__elide_mergeinfo_catalog(svn_
    each path.
 
    Return a pointer to the mergeinfo value of the nearest path-wise ancestor
-   of ABS_REPOS_PATH in DEPTH_FIRST_CATALOG_INDEX.  A path is considered its
-   own ancestor, so if a key exactly matches ABS_REPOS_PATH, return that
+   of FSPATH in DEPTH_FIRST_CATALOG_INDEX.  A path is considered its
+   own ancestor, so if a key exactly matches FSPATH, return that
    key's mergeinfo and set *ANCESTOR_IS_SELF to true (set it to false in all
    other cases).
 
@@ -1275,7 +1218,7 @@ svn_client__elide_mergeinfo_catalog(svn_
 static svn_mergeinfo_t
 find_nearest_ancestor(const apr_array_header_t *depth_first_catalog_index,
                       svn_boolean_t *ancestor_is_self,
-                      const char *abs_repos_path)
+                      const char *fspath)
 {
   int ancestor_index = -1;
 
@@ -1289,12 +1232,12 @@ find_nearest_ancestor(const apr_array_he
         {
           svn_sort__item_t item = APR_ARRAY_IDX(depth_first_catalog_index, i,
                                                 svn_sort__item_t);
-          if (svn_fspath__is_ancestor(item.key, abs_repos_path))
+          if (svn_fspath__skip_ancestor(item.key, fspath))
             {
               ancestor_index = i;
 
-              /* There's no nearer ancestor than ABS_REPOS_PATH itself. */
-              if (strcmp(item.key, abs_repos_path) == 0)
+              /* There's no nearer ancestor than FSPATH itself. */
+              if (strcmp(item.key, fspath) == 0)
                 {
                   *ancestor_is_self = TRUE;
                   break;
@@ -1322,12 +1265,12 @@ struct filter_log_entry_baton_t
 
   /* Unsorted array of repository relative paths representing the merge
      sources.  There will be more than one source  */
-  const apr_array_header_t *merge_source_paths;
+  const apr_array_header_t *merge_source_fspaths;
 
   /* The repository-absolute path we are calling svn_client_log5() on. */
-  const char *abs_repos_target_path;
+  const char *target_fspath;
 
-  /* Mergeinfo catalog for the tree rooted at ABS_REPOS_TARGET_PATH.
+  /* Mergeinfo catalog for the tree rooted at TARGET_FSPATH.
      The path keys must be repository-absolute. */
   svn_mergeinfo_catalog_t target_mergeinfo_catalog;
 
@@ -1353,16 +1296,16 @@ struct filter_log_entry_baton_t
 
    Call the wrapped log receiver BATON->log_receiver (with
    BATON->log_receiver_baton) if:
-   
+
    BATON->FILTERING_MERGED is FALSE and the changes represented by LOG_ENTRY
-   have been fully merged from BATON->MERGE_SOURCE_PATHS to the WC target
+   have been fully merged from BATON->merge_source_fspaths to the WC target
    based on the mergeinfo for the WC contained in BATON->TARGET_MERGEINFO_CATALOG.
 
    Or
 
    BATON->FILTERING_MERGED is TRUE and the changes represented by LOG_ENTRY
    have not been merged, or only partially merged, from
-   BATON->MERGE_SOURCE_PATHS to the WC target based on the mergeinfo for the
+   BATON->merge_source_fspaths to the WC target based on the mergeinfo for the
    WC contained in BATON->TARGET_MERGEINFO_CATALOG. */
 static svn_error_t *
 filter_log_entry_with_rangelist(void *baton,
@@ -1403,11 +1346,12 @@ filter_log_entry_with_rangelist(void *ba
 
   /* If the paths changed by LOG_ENTRY->REVISION are provided we can determine
      if LOG_ENTRY->REVISION, while only partially represented in
-     BATON->RANGELIST, is in fact completely applied to all affected paths. */
+     BATON->RANGELIST, is in fact completely applied to all affected paths.
+     ### And ... what if it is, or if it isn't? What do we do with the answer?
+         And how do we cope if the changed paths are not provided? */
   if ((log_entry->non_inheritable || !fleb->filtering_merged)
       && log_entry->changed_paths2)
     {
-      int i;
       apr_hash_index_t *hi;
       svn_boolean_t all_subtrees_have_this_rev = TRUE;
       apr_array_header_t *this_rev_rangelist =
@@ -1419,27 +1363,27 @@ filter_log_entry_with_rangelist(void *ba
            hi;
            hi = apr_hash_next(hi))
         {
+          int i;
           const char *path = svn__apr_hash_index_key(hi);
           svn_log_changed_path2_t *change = svn__apr_hash_index_val(hi);
-          const char *target_path_affected;
+          const char *target_fspath_affected;
           svn_mergeinfo_t nearest_ancestor_mergeinfo;
-          apr_hash_index_t *hi2;
           svn_boolean_t found_this_revision = FALSE;
           const char *merge_source_rel_target;
-          const char *merge_source_path;
+          const char *merge_source_fspath;
           svn_boolean_t ancestor_is_self;
 
           svn_pool_clear(iterpool);
 
           /* Check that PATH is a subtree of at least one of the
              merge sources.  If not then ignore this path.  */
-          for (i = 0; i < fleb->merge_source_paths->nelts; i++)
+          for (i = 0; i < fleb->merge_source_fspaths->nelts; i++)
             {
-              merge_source_path = APR_ARRAY_IDX(fleb->merge_source_paths,
-                                                i, const char *);
+              merge_source_fspath = APR_ARRAY_IDX(fleb->merge_source_fspaths,
+                                                  i, const char *);
 
               merge_source_rel_target
-                = svn_fspath__skip_ancestor(merge_source_path, path);
+                = svn_fspath__skip_ancestor(merge_source_fspath, path);
               if (merge_source_rel_target)
                 {
                   /* If MERGE_SOURCE was itself deleted, replaced, or added
@@ -1447,24 +1391,24 @@ filter_log_entry_with_rangelist(void *ba
                      can't merge a addition or deletion of yourself. */
                   if (merge_source_rel_target[0] == '\0'
                       && (change->action != 'M'))
-                    i = fleb->merge_source_paths->nelts;
+                    i = fleb->merge_source_fspaths->nelts;
                   break;
                 }
             }
           /* If we examined every merge source path and PATH is a child of
              none of them then we can ignore this PATH. */
-          if (i == fleb->merge_source_paths->nelts)
+          if (i == fleb->merge_source_fspaths->nelts)
             continue;
 
           /* Calculate the target path which PATH would affect if merged. */
-          target_path_affected = svn_fspath__join(fleb->abs_repos_target_path,
-                                                  merge_source_rel_target,
-                                                  iterpool);
+          target_fspath_affected = svn_fspath__join(fleb->target_fspath,
+                                                    merge_source_rel_target,
+                                                    iterpool);
 
           nearest_ancestor_mergeinfo =
             find_nearest_ancestor(fleb->depth_first_catalog_index,
                                   &ancestor_is_self,
-                                  target_path_affected);
+                                  target_fspath_affected);
 
           /* Issue #3791: A path should never have explicit mergeinfo
              describing its own addition (that's self-referential).  Nor will
@@ -1495,6 +1439,8 @@ filter_log_entry_with_rangelist(void *ba
 
           if (nearest_ancestor_mergeinfo)
             {
+              apr_hash_index_t *hi2;
+
               for (hi2 = apr_hash_first(iterpool, nearest_ancestor_mergeinfo);
                    hi2;
                    hi2 = apr_hash_next(hi2))
@@ -1504,11 +1450,11 @@ filter_log_entry_with_rangelist(void *ba
 
                   /* Does the mergeinfo for PATH reflect if
                      LOG_ENTRY->REVISION was previously merged
-                     from MERGE_SOURCE_PATH? */
-                  if (svn_fspath__is_ancestor(merge_source_path,
-                                              mergeinfo_path))
+                     from MERGE_SOURCE_FSPATH? */
+                  if (svn_fspath__skip_ancestor(merge_source_fspath,
+                                                mergeinfo_path))
                     {
-                      /* Something was merged from MERGE_SOURCE_PATH, does
+                      /* Something was merged from MERGE_SOURCE_FSPATH, does
                          it include LOG_ENTRY->REVISION? */
                       SVN_ERR(svn_rangelist_intersect(&intersection,
                                                       rangelist,
@@ -1517,15 +1463,30 @@ filter_log_entry_with_rangelist(void *ba
                                                       iterpool));
                       if (intersection->nelts)
                         {
-                          SVN_ERR(svn_rangelist_intersect(&intersection,
-                                                          rangelist,
-                                                          this_rev_rangelist,
-                                                          TRUE, iterpool));
-                          if (intersection->nelts)
+                          if (ancestor_is_self)
                             {
+                              /* TARGET_PATH_AFFECTED has explicit mergeinfo,
+                                 so we don't need to worry if that mergeinfo
+                                 is inheritable or not. */
                               found_this_revision = TRUE;
                               break;
                             }
+                          else
+                            {
+                              /* TARGET_PATH_AFFECTED inherited its mergeinfo,
+                                 se we have to ignore non-inheritable
+                                 ranges. */
+                              SVN_ERR(svn_rangelist_intersect(
+                                &intersection,
+                                rangelist,
+                                this_rev_rangelist,
+                                TRUE, iterpool));
+                              if (intersection->nelts)
+                                {
+                                  found_this_revision = TRUE;
+                                  break;
+                                }
+                            }
                         }
                     }
                 }
@@ -1557,11 +1518,11 @@ filter_log_entry_with_rangelist(void *ba
 
 static svn_error_t *
 logs_for_mergeinfo_rangelist(const char *source_url,
-                             const apr_array_header_t *merge_source_paths,
+                             const apr_array_header_t *merge_source_fspaths,
                              svn_boolean_t filtering_merged,
                              const apr_array_header_t *rangelist,
                              svn_mergeinfo_t target_mergeinfo_catalog,
-                             const char *abs_repos_target_path,
+                             const char *target_fspath,
                              svn_boolean_t discover_changed_paths,
                              const apr_array_header_t *revprops,
                              svn_log_entry_receiver_t log_receiver,
@@ -1573,7 +1534,6 @@ logs_for_mergeinfo_rangelist(const char 
   svn_merge_range_t *oldest_range, *youngest_range;
   apr_array_header_t *revision_ranges;
   svn_opt_revision_t oldest_rev, youngest_rev;
-  svn_opt_revision_range_t *range;
   struct filter_log_entry_baton_t fleb;
 
   if (! rangelist->nelts)
@@ -1601,35 +1561,19 @@ logs_for_mergeinfo_rangelist(const char 
 
   /* FILTER_LOG_ENTRY_BATON_T->TARGET_MERGEINFO_CATALOG's keys are required
      to be repository-absolute. */
-  if (apr_hash_count(target_mergeinfo_catalog))
-    {
-      apr_hash_index_t *hi;
-      svn_mergeinfo_catalog_t rekeyed_catalog = apr_hash_make(scratch_pool);
-
-      for (hi = apr_hash_first(scratch_pool, target_mergeinfo_catalog);
-           hi;
-           hi = apr_hash_next(hi))
-        {
-          const char *path = svn__apr_hash_index_key(hi);
-
-          if (!svn_dirent_is_absolute(path))
-            apr_hash_set(rekeyed_catalog,
-                         svn_dirent_join("/", path, scratch_pool),
-                         APR_HASH_KEY_STRING,
-                         svn__apr_hash_index_val(hi));
-        }
-      target_mergeinfo_catalog = rekeyed_catalog;
-    }
+  SVN_ERR(svn_mergeinfo__add_prefix_to_catalog(&target_mergeinfo_catalog,
+                                               target_mergeinfo_catalog, "/",
+                                               scratch_pool, scratch_pool));
 
   /* Build the log filtering callback baton. */
   fleb.filtering_merged = filtering_merged;
-  fleb.merge_source_paths = merge_source_paths;
+  fleb.merge_source_fspaths = merge_source_fspaths;
   fleb.target_mergeinfo_catalog = target_mergeinfo_catalog;
   fleb.depth_first_catalog_index =
     svn_sort__hash(target_mergeinfo_catalog,
                    svn_sort_compare_items_as_paths,
                    scratch_pool);
-  fleb.abs_repos_target_path = abs_repos_target_path;
+  fleb.target_fspath = target_fspath;
   fleb.rangelist = rangelist;
   fleb.log_receiver = log_receiver;
   fleb.log_receiver_baton = log_receiver_baton;
@@ -1638,10 +1582,8 @@ logs_for_mergeinfo_rangelist(const char 
   /* Drive the log. */
   revision_ranges = apr_array_make(scratch_pool, 1,
                                    sizeof(svn_opt_revision_range_t *));
-  range = apr_pcalloc(scratch_pool, sizeof(*range));
-  range->end = youngest_rev;
-  range->start = oldest_rev;
-  APR_ARRAY_PUSH(revision_ranges, svn_opt_revision_range_t *) = range;
+  APR_ARRAY_PUSH(revision_ranges, svn_opt_revision_range_t *)
+    = svn_opt__revision_range_create(&oldest_rev, &youngest_rev, scratch_pool);
   SVN_ERR(svn_client_log5(target, &youngest_rev, revision_ranges,
                           0, discover_changed_paths, FALSE, FALSE, revprops,
                           filter_log_entry_with_rangelist, &fleb, ctx,
@@ -1664,7 +1606,6 @@ svn_client_mergeinfo_get_merged(apr_hash
                                 apr_pool_t *pool)
 {
   const char *repos_root;
-  apr_hash_t *full_path_mergeinfo;
   svn_mergeinfo_catalog_t mergeinfo_cat;
   svn_mergeinfo_t mergeinfo;
 
@@ -1690,26 +1631,8 @@ svn_client_mergeinfo_get_merged(apr_hash
       mergeinfo = NULL;
     }
 
-  /* Copy the MERGEINFO hash items into another hash, but change
-     the relative paths into full URLs. */
-  *mergeinfo_p = NULL;
-  if (mergeinfo)
-    {
-      apr_hash_index_t *hi;
-
-      full_path_mergeinfo = apr_hash_make(pool);
-      for (hi = apr_hash_first(pool, mergeinfo); hi; hi = apr_hash_next(hi))
-        {
-          const char *key = svn__apr_hash_index_key(hi);
-          void *val = svn__apr_hash_index_val(hi);
-
-          apr_hash_set(full_path_mergeinfo,
-                       svn_path_url_add_component2(repos_root, key + 1, pool),
-                       APR_HASH_KEY_STRING, val);
-        }
-      *mergeinfo_p = full_path_mergeinfo;
-    }
-
+  SVN_ERR(svn_mergeinfo__relpaths_to_urls(mergeinfo_p, mergeinfo,
+                                          repos_root, pool, pool));
   return SVN_NO_ERROR;
 }
 
@@ -1743,7 +1666,7 @@ svn_client_mergeinfo_log(svn_boolean_t f
   svn_mergeinfo_t target_history;
   apr_array_header_t *master_noninheritable_rangelist;
   apr_array_header_t *master_inheritable_rangelist;
-  apr_array_header_t *merge_source_paths =
+  apr_array_header_t *merge_source_fspaths =
     apr_array_make(scratch_pool, 1, sizeof(const char *));
   apr_hash_index_t *hi_catalog;
   apr_hash_index_t *hi;
@@ -1774,7 +1697,7 @@ svn_client_mergeinfo_log(svn_boolean_t f
                                             ctx->wc_ctx,
                                             target_path_or_url,
                                             repos_root,
-                                            FALSE, NULL,
+                                            FALSE /* leading_slash */, NULL,
                                             scratch_pool,
                                             scratch_pool));
 
@@ -1803,19 +1726,17 @@ svn_client_mergeinfo_log(svn_boolean_t f
   /* Open RA sessions to the repository for the source and target.
    * ### TODO: As the source and target must be in the same repository, we
    * should share a single session, tracking the two URLs separately. */
-  
   if (!finding_merged)
     {
       svn_revnum_t target_peg_revnum;
-      const char *url;
 
       SVN_ERR(svn_client__ra_session_from_path(&target_session,
-                                               &target_peg_revnum, &url,
+                                               &target_peg_revnum, NULL,
                                                target_path_or_url, NULL,
                                                target_peg_revision,
                                                target_peg_revision,
-                                               ctx, scratch_pool));
-      
+                                               ctx, sesspool));
+
       SVN_ERR(svn_client__get_history_as_mergeinfo(&target_history, NULL,
                                                    target_peg_revnum,
                                                    SVN_INVALID_REVNUM,
@@ -1823,17 +1744,15 @@ svn_client_mergeinfo_log(svn_boolean_t f
                                                    target_session, ctx,
                                                    scratch_pool));
     }
-
   {
     svn_revnum_t source_peg_revnum;
-    const char *url;
 
     SVN_ERR(svn_client__ra_session_from_path(&source_session,
-                                             &source_peg_revnum, &url,
+                                             &source_peg_revnum, NULL,
                                              source_path_or_url, NULL,
                                              source_peg_revision,
                                              source_peg_revision,
-                                             ctx, scratch_pool));
+                                             ctx, sesspool));
 
     SVN_ERR(svn_client__get_history_as_mergeinfo(&source_history, NULL,
                                                  source_peg_revnum,
@@ -1842,7 +1761,7 @@ svn_client_mergeinfo_log(svn_boolean_t f
                                                  source_session, ctx,
                                                  scratch_pool));
   }
-
+  /* Close the source and target sessions. */
   svn_pool_destroy(sesspool);
 
   /* Separate the explicit or inherited mergeinfo on TARGET_PATH_OR_URL, and possibly
@@ -1905,9 +1824,9 @@ svn_client_mergeinfo_log(svn_boolean_t f
                                            subtree_history,
                                            subtree_source_history, TRUE,
                                            scratch_pool, iterpool));
-          SVN_ERR(svn_mergeinfo_merge(subtree_mergeinfo,
-                                      merged_via_history,
-                                      scratch_pool));
+          SVN_ERR(svn_mergeinfo_merge2(subtree_mergeinfo,
+                                       merged_via_history,
+                                       scratch_pool, scratch_pool));
         }
 
       SVN_ERR(svn_mergeinfo_inheritable2(&subtree_inheritable_mergeinfo,
@@ -1962,8 +1881,7 @@ svn_client_mergeinfo_log(svn_boolean_t f
           SVN_ERR(svn_rangelist__merge_many(subtree_merged_rangelist,
                                             merged, scratch_pool, iterpool));
 
-          apr_hash_set(inheritable_subtree_merges,
-                       apr_pstrdup(scratch_pool, subtree_path),
+          apr_hash_set(inheritable_subtree_merges, subtree_path,
                        APR_HASH_KEY_STRING, subtree_merged_rangelist);
         }
       else
@@ -1971,8 +1889,7 @@ svn_client_mergeinfo_log(svn_boolean_t f
           /* Map SUBTREE_PATH to an empty rangelist if there was nothing
              fully merged. e.g. Only empty or non-inheritable mergienfo
              on the subtree or mergeinfo unrelated to the source. */
-          apr_hash_set(inheritable_subtree_merges,
-                       apr_pstrdup(scratch_pool, subtree_path),
+          apr_hash_set(inheritable_subtree_merges, subtree_path,
                        APR_HASH_KEY_STRING,
                        apr_array_make(scratch_pool, 0,
                        sizeof(svn_merge_range_t *)));
@@ -2064,10 +1981,10 @@ svn_client_mergeinfo_log(svn_boolean_t f
   else
     {
       /* Determine the correct (youngest) target for 'svn log'. */
-      svn_merge_range_t *youngest_range = svn_merge_range_dup(
-        APR_ARRAY_IDX(master_inheritable_rangelist,
-        master_inheritable_rangelist->nelts - 1,
-        svn_merge_range_t *), scratch_pool);
+      svn_merge_range_t *youngest_range
+        = APR_ARRAY_IDX(master_inheritable_rangelist,
+                        master_inheritable_rangelist->nelts - 1,
+                        svn_merge_range_t *);
       apr_array_header_t *youngest_rangelist =
         svn_rangelist__initialize(youngest_range->end - 1,
                                   youngest_range->end,
@@ -2082,17 +1999,17 @@ svn_client_mergeinfo_log(svn_boolean_t f
           apr_array_header_t *subtree_merged_rangelist =
             svn__apr_hash_index_val(hi);
           apr_array_header_t *intersecting_rangelist;
+
           svn_pool_clear(iterpool);
           SVN_ERR(svn_rangelist_intersect(&intersecting_rangelist,
                                           youngest_rangelist,
                                           subtree_merged_rangelist,
                                           FALSE, iterpool));
 
-          APR_ARRAY_PUSH(merge_source_paths, const char *) =
-            apr_pstrdup(scratch_pool, key);
+          APR_ARRAY_PUSH(merge_source_fspaths, const char *) = key;
 
           if (intersecting_rangelist->nelts)
-            log_target = apr_pstrdup(scratch_pool, key);
+            log_target = key;
         }
     }
 
@@ -2104,13 +2021,12 @@ svn_client_mergeinfo_log(svn_boolean_t f
   log_target = svn_path_url_add_component2(repos_root, log_target + 1,
                                            scratch_pool);
 
-  SVN_ERR(logs_for_mergeinfo_rangelist(log_target, merge_source_paths,
+  SVN_ERR(logs_for_mergeinfo_rangelist(log_target, merge_source_fspaths,
                                        finding_merged,
                                        master_inheritable_rangelist,
                                        target_mergeinfo_cat,
-                                       svn_dirent_join("/",
-                                                       target_repos_rel,
-                                                       scratch_pool),
+                                       svn_fspath__join("/", target_repos_rel,
+                                                        scratch_pool),
                                        discover_changed_paths,
                                        revprops,
                                        log_receiver, log_receiver_baton,

Modified: subversion/branches/fs-py/subversion/libsvn_client/mergeinfo.h
URL: http://svn.apache.org/viewvc/subversion/branches/fs-py/subversion/libsvn_client/mergeinfo.h?rev=1220893&r1=1220892&r2=1220893&view=diff
==============================================================================
--- subversion/branches/fs-py/subversion/libsvn_client/mergeinfo.h (original)
+++ subversion/branches/fs-py/subversion/libsvn_client/mergeinfo.h Mon Dec 19 18:49:34 2011
@@ -40,7 +40,9 @@ typedef struct svn_client__merge_path_t
 {
   const char *abspath;               /* Absolute working copy path. */
   svn_boolean_t missing_child;       /* ABSPATH has an immediate child which
-                                        is missing. */
+                                        is missing, but is not switched. */
+  svn_boolean_t switched_child;      /* ABSPATH has an immediate child which
+                                        is switched. */
   svn_boolean_t switched;            /* ABSPATH is switched. */
   svn_boolean_t has_noninheritable;  /* ABSPATH has svn:mergeinfo set on it
                                         which includes non-inheritable
@@ -79,6 +81,13 @@ typedef struct svn_client__merge_path_t
                                            to the merge, and the operational
                                            depth of the merge is
                                            svn_depth_immediates. */
+  svn_boolean_t record_mergeinfo;       /* Mergeinfo needs to be recorded
+                                           on ABSPATH to describe the
+                                           merge. */
+  svn_boolean_t record_noninheritable;  /* Non-inheritable mergeinfo needs to
+                                           be recorded on ABSPATH to describe
+                                           the merge. Implies RECORD_MERGEINFO
+                                           is true. */
 } svn_client__merge_path_t;
 
 /* Return a deep copy of the merge-path structure OLD, allocated in POOL. */
@@ -86,13 +95,21 @@ svn_client__merge_path_t *
 svn_client__merge_path_dup(const svn_client__merge_path_t *old,
                            apr_pool_t *pool);
 
+/* Create a new merge path structure, allocated in POOL.  Initialize the
+ * 'abspath' member to a deep copy of ABSPATH and all other fields to zero
+ * bytes. */
+svn_client__merge_path_t *
+svn_client__merge_path_create(const char *abspath,
+                              apr_pool_t *pool);
+
 
 
 /*** Functions ***/
 
 /* Find explicit or inherited WC mergeinfo for LOCAL_ABSPATH, and return it
    in *MERGEINFO (NULL if no mergeinfo is set).  Set *INHERITED to
-   whether the mergeinfo was inherited (TRUE or FALSE).
+   whether the mergeinfo was inherited (TRUE or FALSE), if INHERITED is
+   non-null.
 
    This function will search for inherited mergeinfo in the parents of
    LOCAL_ABSPATH only if the base revision of LOCAL_ABSPATH falls within
@@ -166,10 +183,12 @@ svn_client__get_wc_mergeinfo_catalog(svn
 
    If there is no mergeinfo available for REL_PATH, or if the server
    doesn't support a mergeinfo capability and SQUELCH_INCAPABLE is
-   TRUE, set *TARGET_MERGEINFO to NULL. */
+   TRUE, set *TARGET_MERGEINFO to NULL. If the server doesn't support
+   a mergeinfo capability and SQUELCH_INCAPABLE is FALSE, return an
+   SVN_ERR_UNSUPPORTED_FEATURE error. */
 svn_error_t *
-svn_client__get_repos_mergeinfo(svn_ra_session_t *ra_session,
-                                svn_mergeinfo_t *target_mergeinfo,
+svn_client__get_repos_mergeinfo(svn_mergeinfo_t *target_mergeinfo,
+                                svn_ra_session_t *ra_session,
                                 const char *rel_path,
                                 svn_revnum_t rev,
                                 svn_mergeinfo_inheritance_t inherit,
@@ -224,7 +243,7 @@ svn_client__get_repos_mergeinfo_catalog(
 
    If TARGET_WCPATH inherited its mergeinfo from a working copy ancestor
    or if it was obtained from the repository, set *INHERITED to TRUE, set it
-   to FALSE otherwise. */
+   to FALSE otherwise, if INHERITED is non-null. */
 svn_error_t *
 svn_client__get_wc_or_repos_mergeinfo(svn_mergeinfo_t *target_mergeinfo,
                                       svn_boolean_t *inherited,
@@ -287,7 +306,7 @@ svn_client__get_history_as_mergeinfo(svn
                                      apr_pool_t *pool);
 
 /* Parse any explicit mergeinfo on LOCAL_ABSPATH and store it in
-   MERGEINFO.  If no record of any mergeinfo exists, set MERGEINFO to NULL.
+   *MERGEINFO.  If no record of any mergeinfo exists, set *MERGEINFO to NULL.
    Does not acount for inherited mergeinfo. */
 svn_error_t *
 svn_client__parse_mergeinfo(svn_mergeinfo_t *mergeinfo,
@@ -311,6 +330,13 @@ svn_client__record_wc_mergeinfo(const ch
                                 svn_client_ctx_t *ctx,
                                 apr_pool_t *scratch_pool);
 
+/* Write mergeinfo into the WC.  RESULT_CATALOG maps (const char *) WC paths
+ * to (svn_mergeinfo_t) mergeinfo. */
+svn_error_t *
+svn_client__record_wc_mergeinfo_catalog(apr_hash_t *result_catalog,
+                                        svn_client_ctx_t *ctx,
+                                        apr_pool_t *scratch_pool);
+
 /* Elide any svn:mergeinfo set on TARGET_WCPATH to its nearest working
    copy (or possibly repository) ancestor with equivalent mergeinfo.
 
@@ -350,21 +376,18 @@ svn_client__elide_mergeinfo(const char *
                             svn_client_ctx_t *ctx,
                             apr_pool_t *pool);
 
-/* TODO(reint): Document. */
-svn_error_t *
-svn_client__elide_mergeinfo_catalog(svn_mergeinfo_t mergeinfo_catalog,
-                                    apr_pool_t *pool);
+/* Simplify a mergeinfo catalog, if possible, via elision.
 
-/* For each source path : rangelist pair in MERGEINFO, append REL_PATH to
-   the source path and add the new source path : rangelist pair to
-   ADJUSTED_MERGEINFO.  The new source path and rangelist are both deep
-   copies allocated in POOL.  Neither ADJUSTED_MERGEINFO
-   nor MERGEINFO should be NULL. */
-svn_error_t *
-svn_client__adjust_mergeinfo_source_paths(svn_mergeinfo_t adjusted_mergeinfo,
-                                          const char *rel_path,
-                                          svn_mergeinfo_t mergeinfo,
-                                          apr_pool_t *pool);
+   For each path in MERGEINFO_CATALOG, check if the path's mergeinfo can
+   elide to the path's nearest path-wise parent in MERGEINFO_CATALOG.  If
+   so, remove that path from MERGEINFO_CATALOG.  Elidability is determined
+   as per svn_client__elide_mergeinfo except that elision to the repository
+   is not considered.
+
+   SCRATCH_POOL is used for temporary allocations. */
+svn_error_t *
+svn_client__elide_mergeinfo_catalog(svn_mergeinfo_catalog_t mergeinfo_catalog,
+                                    apr_pool_t *scratch_pool);
 
 /* Set *MERGEINFO_CHANGES to TRUE if LOCAL_ABSPATH has locally modified
    mergeinfo, set *MERGEINFO_CHANGES to FALSE otherwise. */

Modified: subversion/branches/fs-py/subversion/libsvn_client/patch.c
URL: http://svn.apache.org/viewvc/subversion/branches/fs-py/subversion/libsvn_client/patch.c?rev=1220893&r1=1220892&r2=1220893&view=diff
==============================================================================
--- subversion/branches/fs-py/subversion/libsvn_client/patch.c (original)
+++ subversion/branches/fs-py/subversion/libsvn_client/patch.c Mon Dec 19 18:49:34 2011
@@ -670,7 +670,7 @@ init_prop_target(prop_patch_target_t **p
     }
   content->existed = (value != NULL);
   new_prop_target->value = value;
-  new_prop_target->patched_value = svn_stringbuf_create("", result_pool);
+  new_prop_target->patched_value = svn_stringbuf_create_empty(result_pool);
 
 
   /* Wire up the read and write callbacks. */
@@ -811,13 +811,20 @@ write_file(void *baton, const char *buf,
  * with the fewest path components, the shortest basename, and the shortest
  * total file name length (in that order). In case of a tie, return the new
  * filename. This heuristic is also used by Larry Wall's UNIX patch (except
- * that it prompts for a filename in case of a tie). */
+ * that it prompts for a filename in case of a tie).
+ * Additionally, for compatibility with git, if one of the filenames
+ * is "/dev/null", use the other filename. */
 static const char *
 choose_target_filename(const svn_patch_t *patch)
 {
   apr_size_t old;
   apr_size_t new;
 
+  if (strcmp(patch->old_filename, "/dev/null") == 0)
+    return patch->new_filename;
+  if (strcmp(patch->new_filename, "/dev/null") == 0)
+    return patch->old_filename;
+
   old = svn_path_component_count(patch->old_filename);
   new = svn_path_component_count(patch->new_filename);
 
@@ -1692,7 +1699,7 @@ apply_hunk(patch_target_t *target, targe
                                                    &eol_str, &eof,
                                                    iterpool, iterpool));
       lines_read++;
-      if (! eof && lines_read > hi->fuzz &&
+      if (lines_read > hi->fuzz &&
           lines_read <= svn_diff_hunk_get_modified_length(hi->hunk) - hi->fuzz)
         {
           apr_size_t len;
@@ -2723,6 +2730,7 @@ delete_empty_dirs(apr_array_header_t *ta
         SVN_ERR(ctx->cancel_func(ctx->cancel_baton));
 
       target_info = APR_ARRAY_IDX(targets_info, i, patch_target_info_t *);
+
       parent = svn_dirent_dirname(target_info->local_abspath, iterpool);
 
       if (apr_hash_get(non_empty_dirs, parent, APR_HASH_KEY_STRING))
@@ -2825,54 +2833,36 @@ delete_empty_dirs(apr_array_header_t *ta
   return SVN_NO_ERROR;
 }
 
-/* Baton for apply_patches(). */
-typedef struct apply_patches_baton_t {
-  /* The path to the patch file. */
-  const char *patch_abspath;
-
-  /* The abspath to the working copy the patch should be applied to. */
-  const char *abs_wc_path;
-
-  /* Indicates whether we're doing a dry run. */
-  svn_boolean_t dry_run;
-
-  /* Number of leading components to strip from patch target paths. */
-  int strip_count;
-
-  /* Whether to apply the patch in reverse. */
-  svn_boolean_t reverse;
-
-  /* Indicates whether we should ignore whitespace when matching context
-   * lines */
-  svn_boolean_t ignore_whitespace;
-
-  /* As in svn_client_patch(). */
-  svn_boolean_t remove_tempfiles;
-
-  /* As in svn_client_patch(). */
-  svn_client_patch_func_t patch_func;
-  void *patch_baton;
-
-  /* The client context. */
-  svn_client_ctx_t *ctx;
-} apply_patches_baton_t;
-
-/* Callback for use with svn_wc__call_with_write_lock().
- * This function is the main entry point into the patch code. */
+/* This function is the main entry point into the patch code. */
 static svn_error_t *
-apply_patches(void *baton,
-              apr_pool_t *result_pool,
+apply_patches(/* The path to the patch file. */
+              const char *patch_abspath,
+              /* The abspath to the working copy the patch should be applied to. */
+              const char *abs_wc_path,
+              /* Indicates whether we're doing a dry run. */
+              svn_boolean_t dry_run,
+              /* Number of leading components to strip from patch target paths. */
+              int strip_count,
+              /* Whether to apply the patch in reverse. */
+              svn_boolean_t reverse,
+              /* Whether to ignore whitespace when matching context lines. */
+              svn_boolean_t ignore_whitespace,
+              /* As in svn_client_patch(). */
+              svn_boolean_t remove_tempfiles,
+              /* As in svn_client_patch(). */
+              svn_client_patch_func_t patch_func,
+              void *patch_baton,
+              /* The client context. */
+              svn_client_ctx_t *ctx,
               apr_pool_t *scratch_pool)
 {
   svn_patch_t *patch;
   apr_pool_t *iterpool;
   svn_patch_file_t *patch_file;
   apr_array_header_t *targets_info;
-  apply_patches_baton_t *btn = baton;
 
   /* Try to open the patch file. */
-  SVN_ERR(svn_diff_open_patch_file(&patch_file, btn->patch_abspath,
-                                   scratch_pool));
+  SVN_ERR(svn_diff_open_patch_file(&patch_file, patch_abspath, scratch_pool));
 
   /* Apply patches. */
   targets_info = apr_array_make(scratch_pool, 0,
@@ -2882,23 +2872,21 @@ apply_patches(void *baton,
     {
       svn_pool_clear(iterpool);
 
-      if (btn->ctx->cancel_func)
-        SVN_ERR(btn->ctx->cancel_func(btn->ctx->cancel_baton));
+      if (ctx->cancel_func)
+        SVN_ERR(ctx->cancel_func(ctx->cancel_baton));
 
       SVN_ERR(svn_diff_parse_next_patch(&patch, patch_file,
-                                        btn->reverse, btn->ignore_whitespace,
+                                        reverse, ignore_whitespace,
                                         iterpool, iterpool));
       if (patch)
         {
           patch_target_t *target;
 
-          SVN_ERR(apply_one_patch(&target, patch, btn->abs_wc_path,
-                                  btn->ctx->wc_ctx, btn->strip_count,
-                                  btn->ignore_whitespace,
-                                  btn->remove_tempfiles,
-                                  btn->patch_func, btn->patch_baton,
-                                  btn->ctx->cancel_func,
-                                  btn->ctx->cancel_baton,
+          SVN_ERR(apply_one_patch(&target, patch, abs_wc_path,
+                                  ctx->wc_ctx, strip_count,
+                                  ignore_whitespace, remove_tempfiles,
+                                  patch_func, patch_baton,
+                                  ctx->cancel_func, ctx->cancel_baton,
                                   iterpool, iterpool));
           if (! target->filtered)
             {
@@ -2908,35 +2896,32 @@ apply_patches(void *baton,
               target_info->local_abspath = apr_pstrdup(scratch_pool,
                                                        target->local_abspath);
               target_info->deleted = target->deleted;
-              APR_ARRAY_PUSH(targets_info,
-                             patch_target_info_t *) = target_info;
 
               if (! target->skipped)
                 {
+                  APR_ARRAY_PUSH(targets_info,
+                                 patch_target_info_t *) = target_info;
+
                   if (target->has_text_changes
                       || target->added
                       || target->deleted)
-                    SVN_ERR(install_patched_target(target, btn->abs_wc_path,
-                                                   btn->ctx, btn->dry_run,
-                                                   iterpool));
+                    SVN_ERR(install_patched_target(target, abs_wc_path,
+                                                   ctx, dry_run, iterpool));
 
                   if (target->has_prop_changes && (!target->deleted))
-                    SVN_ERR(install_patched_prop_targets(target, btn->ctx,
-                                                         btn->dry_run,
-                                                         iterpool));
+                    SVN_ERR(install_patched_prop_targets(target, ctx,
+                                                         dry_run, iterpool));
 
-                  SVN_ERR(write_out_rejected_hunks(target, btn->dry_run,
-                                                   iterpool));
+                  SVN_ERR(write_out_rejected_hunks(target, dry_run, iterpool));
                 }
-              SVN_ERR(send_patch_notification(target, btn->ctx, iterpool));
+              SVN_ERR(send_patch_notification(target, ctx, iterpool));
             }
         }
     }
   while (patch);
 
   /* Delete directories which are empty after patching, if any. */
-  SVN_ERR(delete_empty_dirs(targets_info, btn->ctx, btn->dry_run,
-                            scratch_pool));
+  SVN_ERR(delete_empty_dirs(targets_info, ctx, dry_run, scratch_pool));
 
   SVN_ERR(svn_diff_close_patch_file(patch_file, iterpool));
   svn_pool_destroy(iterpool);
@@ -2957,7 +2942,6 @@ svn_client_patch(const char *patch_abspa
                  svn_client_ctx_t *ctx,
                  apr_pool_t *scratch_pool)
 {
-  apply_patches_baton_t baton;
   svn_node_kind_t kind;
 
   if (strip_count < 0)
@@ -2994,19 +2978,10 @@ svn_client_patch(const char *patch_abspa
                              svn_dirent_local_style(wc_dir_abspath,
                                                     scratch_pool));
 
-  baton.patch_abspath = patch_abspath;
-  baton.abs_wc_path = wc_dir_abspath;
-  baton.dry_run = dry_run;
-  baton.ctx = ctx;
-  baton.strip_count = strip_count;
-  baton.reverse = reverse;
-  baton.ignore_whitespace = ignore_whitespace;
-  baton.remove_tempfiles = remove_tempfiles;
-  baton.patch_func = patch_func;
-  baton.patch_baton = patch_baton;
-
-  return svn_error_trace(
-           svn_wc__call_with_write_lock(apply_patches, &baton,
-                                        ctx->wc_ctx, wc_dir_abspath, FALSE,
-                                        scratch_pool, scratch_pool));
+  SVN_WC__CALL_WITH_WRITE_LOCK(
+    apply_patches(patch_abspath, wc_dir_abspath, dry_run, strip_count,
+                  reverse, ignore_whitespace, remove_tempfiles,
+                  patch_func, patch_baton, ctx, scratch_pool),
+    ctx->wc_ctx, wc_dir_abspath, FALSE /* lock_anchor */, scratch_pool);
+  return SVN_NO_ERROR;
 }

Modified: subversion/branches/fs-py/subversion/libsvn_client/prop_commands.c
URL: http://svn.apache.org/viewvc/subversion/branches/fs-py/subversion/libsvn_client/prop_commands.c?rev=1220893&r1=1220892&r2=1220893&view=diff
==============================================================================
--- subversion/branches/fs-py/subversion/libsvn_client/prop_commands.c (original)
+++ subversion/branches/fs-py/subversion/libsvn_client/prop_commands.c Mon Dec 19 18:49:34 2011
@@ -248,37 +248,6 @@ propset_on_url(const char *propname,
   return editor->close_edit(edit_baton, pool);
 }
 
-/* Baton for set_props_cb */
-struct set_props_baton
-{
-  svn_client_ctx_t *ctx;
-  const char *local_abspath;
-  svn_depth_t depth;
-  svn_node_kind_t kind;
-  const char *propname;
-  const svn_string_t *propval;
-  svn_boolean_t skip_checks;
-  const apr_array_header_t *changelist_filter;
-};
-
-/* Working copy lock callback for svn_client_propset4 */
-static svn_error_t *
-set_props_cb(void *baton,
-             apr_pool_t *result_pool,
-             apr_pool_t *scratch_pool)
-{
-  struct set_props_baton *bt = baton;
-
-  SVN_ERR(svn_wc_prop_set4(bt->ctx->wc_ctx, bt->local_abspath, bt->propname,
-                           bt->propval, bt->depth, bt->skip_checks,
-                           bt->changelist_filter,
-                           bt->ctx->cancel_func, bt->ctx->cancel_baton,
-                           bt->ctx->notify_func2, bt->ctx->notify_baton2,
-                           scratch_pool));
-
-  return SVN_NO_ERROR;
-}
-
 /* Check that PROPNAME is a valid name for a versioned property.  Return an
  * error if it is not valid, specifically if it is:
  *   - the name of a standard Subversion rev-prop; or
@@ -342,7 +311,6 @@ svn_client_propset_local(const char *pro
       svn_node_kind_t kind;
       const char *target_abspath;
       svn_error_t *err;
-      struct set_props_baton baton;
       const char *target = APR_ARRAY_IDX(targets, i, const char *);
 
       svn_pool_clear(iterpool);
@@ -374,18 +342,12 @@ svn_client_propset_local(const char *pro
       else
         SVN_ERR(err);
 
-      baton.ctx = ctx;
-      baton.local_abspath = target_abspath;
-      baton.depth = depth;
-      baton.kind = kind;
-      baton.propname = propname;
-      baton.propval = propval;
-      baton.skip_checks = skip_checks;
-      baton.changelist_filter = changelists;
-
-      SVN_ERR(svn_wc__call_with_write_lock(set_props_cb, &baton,
-                                           ctx->wc_ctx, target_abspath,
-                                           FALSE, iterpool, iterpool));
+      SVN_WC__CALL_WITH_WRITE_LOCK(
+        svn_wc_prop_set4(ctx->wc_ctx, target_abspath, propname,
+                         propval, depth, skip_checks, changelists,
+                         ctx->cancel_func, ctx->cancel_baton,
+                         ctx->notify_func2, ctx->notify_baton2, iterpool),
+        ctx->wc_ctx, target_abspath, FALSE /* lock_anchor */, iterpool);
     }
   svn_pool_destroy(iterpool);
 

Modified: subversion/branches/fs-py/subversion/libsvn_client/ra.c
URL: http://svn.apache.org/viewvc/subversion/branches/fs-py/subversion/libsvn_client/ra.c?rev=1220893&r1=1220892&r2=1220893&view=diff
==============================================================================
--- subversion/branches/fs-py/subversion/libsvn_client/ra.c (original)
+++ subversion/branches/fs-py/subversion/libsvn_client/ra.c Mon Dec 19 18:49:34 2011
@@ -40,6 +40,7 @@
 
 #include "svn_private_config.h"
 #include "private/svn_wc_private.h"
+#include "private/svn_client_private.h"
 
 
 /* This is the baton that we pass svn_ra_open3(), and is associated with
@@ -385,56 +386,8 @@ svn_client_open_ra_session(svn_ra_sessio
 }
 
 
-svn_error_t *
-svn_client_uuid_from_url(const char **uuid,
-                         const char *url,
-                         svn_client_ctx_t *ctx,
-                         apr_pool_t *pool)
-{
-  svn_ra_session_t *ra_session;
-  apr_pool_t *subpool = svn_pool_create(pool);
-
-  /* use subpool to create a temporary RA session */
-  SVN_ERR(svn_client__open_ra_session_internal(&ra_session, NULL, url,
-                                               NULL, /* no base dir */
-                                               NULL, FALSE, TRUE,
-                                               ctx, subpool));
-
-  SVN_ERR(svn_ra_get_uuid2(ra_session, uuid, pool));
-
-  /* destroy the RA session */
-  svn_pool_destroy(subpool);
-
-  return SVN_NO_ERROR;
-}
-
-
-svn_error_t *
-svn_client_uuid_from_path2(const char **uuid,
-                           const char *local_abspath,
-                           svn_client_ctx_t *ctx,
-                           apr_pool_t *result_pool,
-                           apr_pool_t *scratch_pool)
-{
-  return svn_error_trace(
-    svn_wc__node_get_repos_info(NULL, uuid, ctx->wc_ctx, local_abspath,
-                                result_pool, scratch_pool));
-}
-
-
 
 
-/* Convert a path or URL for display: if it is a local path, convert it to
- * the local path style; if it is a URL, return it unchanged. */
-static const char *
-path_or_url_local_style(const char *path_or_url,
-                        apr_pool_t *pool)
-{
-  if (svn_path_is_url(path_or_url))
-    return path_or_url;
-  return svn_dirent_local_style(path_or_url, pool);
-}
-
 /* Given PATH_OR_URL, which contains either a working copy path or an
    absolute URL, a peg revision PEG_REVISION, and a desired revision
    REVISION, find the path at which that object exists in REVISION,
@@ -443,11 +396,17 @@ path_or_url_local_style(const char *path
    PEG_REVISION and REVISION, and return @c
    SVN_ERR_CLIENT_UNRELATED_RESOURCES if it is not the same node.
 
-   If PEG_REVISION's kind is svn_opt_revision_unspecified, interpret it
-   as "head" for a URL or "working" for a working-copy path.
+   If PEG_REVISION->kind is 'unspecified', the peg revision is 'head'
+   for a URL or 'working' for a WC path.  If REVISION->kind is
+   'unspecified', the operative revision is the peg revision.
 
    Store the actual revision number of the object in *REV_P, and the
-   final resulting URL in *URL_P.
+   final resulting URL in *URL_P. REV_P and/or URL_P may be NULL if not
+   wanted.
+
+   RA_SESSION should be an open RA session pointing at the URL of
+   PATH_OR_URL, or NULL, in which case this function will open its own
+   temporary session.
 
    Use authentication baton cached in CTX to authenticate against the
    repository.
@@ -465,28 +424,25 @@ resolve_rev_and_url(svn_revnum_t *rev_p,
 {
   svn_opt_revision_t peg_rev = *peg_revision;
   svn_opt_revision_t start_rev = *revision;
-  svn_opt_revision_t *good_rev;
   const char *url;
   svn_revnum_t rev;
 
+  /* Default revisions: peg -> working or head; operative -> peg. */
   SVN_ERR(svn_opt_resolve_revisions(&peg_rev, &start_rev,
                                     svn_path_is_url(path_or_url),
-                                    TRUE,
+                                    TRUE /* notice_local_mods */,
                                     pool));
 
   /* Run the history function to get the object's (possibly
      different) url in REVISION. */
-  SVN_ERR(svn_client__repos_locations(&url, &good_rev, NULL, NULL,
+  SVN_ERR(svn_client__repos_locations(&url, &rev, NULL, NULL,
                                       ra_session, path_or_url, &peg_rev,
                                       &start_rev, NULL, ctx, pool));
 
-  /* Resolve good_rev into a real revnum. */
-  if (good_rev->kind == svn_opt_revision_unspecified)
-    good_rev->kind = svn_opt_revision_head;
-  SVN_ERR(svn_client__get_revision_number(&rev, NULL, ctx->wc_ctx, url,
-                                          ra_session, good_rev, pool));
-  *rev_p = rev;
-  *url_p = url;
+  if (rev_p)
+    *rev_p = rev;
+  if (url_p)
+    *url_p = url;
 
   return SVN_NO_ERROR;
 }
@@ -505,6 +461,7 @@ svn_client__ra_session_from_path(svn_ra_
   svn_ra_session_t *ra_session;
   const char *initial_url;
   const char *corrected_url;
+  const char *resolved_url;
 
   SVN_ERR(svn_client_url_from_path2(&initial_url, path_or_url, ctx, pool,
                                     pool));
@@ -523,14 +480,16 @@ svn_client__ra_session_from_path(svn_ra_
   if (corrected_url && svn_path_is_url(path_or_url))
     path_or_url = corrected_url;
 
-  SVN_ERR(resolve_rev_and_url(rev_p, url_p, ra_session,
+  SVN_ERR(resolve_rev_and_url(rev_p, &resolved_url, ra_session,
                               path_or_url, peg_revision, revision,
                               ctx, pool));
 
   /* Make the session point to the real URL. */
-  SVN_ERR(svn_ra_reparent(ra_session, *url_p, pool));
+  SVN_ERR(svn_ra_reparent(ra_session, resolved_url, pool));
 
   *ra_session_p = ra_session;
+  if (url_p)
+    *url_p = resolved_url;
 
   return SVN_NO_ERROR;
 }
@@ -614,12 +573,106 @@ svn_client__repos_location_segments(apr_
   return SVN_NO_ERROR;
 }
 
+/* Set *START_URL and *END_URL to the URLs that the object URL@PEG_REVNUM
+ * had in revisions START_REVNUM and END_REVNUM.  Return an error if the
+ * node cannot be traced back to one of the requested revisions.
+ *
+ * START_URL and/or END_URL may be NULL if not wanted.  START_REVNUM and
+ * END_REVNUM must be valid revision numbers except that END_REVNUM may
+ * be SVN_INVALID_REVNUM if END_URL is NULL.  RA_SESSION is required.
+ */
+static svn_error_t *
+repos_locations(const char **start_url,
+                const char **end_url,
+                svn_ra_session_t *ra_session,
+                const char *url,
+                svn_revnum_t peg_revnum,
+                svn_revnum_t start_revnum,
+                svn_revnum_t end_revnum,
+                apr_pool_t *result_pool,
+                apr_pool_t *scratch_pool)
+{
+  const char *repos_url, *start_path, *end_path;
+  apr_array_header_t *revs;
+  apr_hash_t *rev_locs;
+
+  SVN_ERR_ASSERT(peg_revnum != SVN_INVALID_REVNUM);
+  SVN_ERR_ASSERT(start_revnum != SVN_INVALID_REVNUM);
+  SVN_ERR_ASSERT(end_revnum != SVN_INVALID_REVNUM || end_url == NULL);
+
+  /* Avoid a network request in the common easy case. */
+  if (start_revnum == peg_revnum
+      && (end_revnum == peg_revnum || end_revnum == SVN_INVALID_REVNUM))
+    {
+      if (start_url)
+        *start_url = url;
+      if (end_url)
+        *end_url = url;
+      return SVN_NO_ERROR;
+    }
+
+  SVN_ERR(svn_ra_get_repos_root2(ra_session, &repos_url, scratch_pool));
+
+  revs = apr_array_make(scratch_pool, 2, sizeof(svn_revnum_t));
+  APR_ARRAY_PUSH(revs, svn_revnum_t) = start_revnum;
+  if (end_revnum != start_revnum && end_revnum != SVN_INVALID_REVNUM)
+    APR_ARRAY_PUSH(revs, svn_revnum_t) = end_revnum;
+
+  SVN_ERR(svn_ra_get_locations(ra_session, &rev_locs, "", peg_revnum,
+                               revs, scratch_pool));
+
+  /* We'd better have all the paths we were looking for! */
+  if (start_url)
+    {
+      start_path = apr_hash_get(rev_locs, &start_revnum, sizeof(svn_revnum_t));
+      if (! start_path)
+        return svn_error_createf
+          (SVN_ERR_CLIENT_UNRELATED_RESOURCES, NULL,
+           _("Unable to find repository location for '%s' in revision %ld"),
+           url, start_revnum);
+      *start_url = svn_path_url_add_component2(repos_url, start_path + 1,
+                                               result_pool);
+    }
+
+  if (end_url)
+    {
+      end_path = apr_hash_get(rev_locs, &end_revnum, sizeof(svn_revnum_t));
+      if (! end_path)
+        return svn_error_createf
+          (SVN_ERR_CLIENT_UNRELATED_RESOURCES, NULL,
+           _("The location for '%s' for revision %ld does not exist in the "
+             "repository or refers to an unrelated object"),
+           url, end_revnum);
+
+      *end_url = svn_path_url_add_component2(repos_url, end_path + 1,
+                                             result_pool);
+    }
+
+  return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_client__repos_location(const char **op_url,
+                           svn_ra_session_t *ra_session,
+                           const char *peg_url,
+                           svn_revnum_t peg_revnum,
+                           svn_revnum_t op_revnum,
+                           svn_client_ctx_t *ctx,
+                           apr_pool_t *result_pool,
+                           apr_pool_t *scratch_pool)
+{
+  SVN_ERR(repos_locations(op_url, NULL,
+                          ra_session, peg_url, peg_revnum,
+                          op_revnum, SVN_INVALID_REVNUM,
+                          result_pool, scratch_pool));
+  return SVN_NO_ERROR;
+}
 
 svn_error_t *
 svn_client__repos_locations(const char **start_url,
-                            svn_opt_revision_t **start_revision,
+                            svn_revnum_t *start_revision,
                             const char **end_url,
-                            svn_opt_revision_t **end_revision,
+                            svn_revnum_t *end_revision,
                             svn_ra_session_t *ra_session,
                             const char *path,
                             const svn_opt_revision_t *revision,
@@ -628,16 +681,11 @@ svn_client__repos_locations(const char *
                             svn_client_ctx_t *ctx,
                             apr_pool_t *pool)
 {
-  const char *repos_url;
   const char *url;
-  const char *start_path = NULL;
-  const char *end_path = NULL;
   const char *local_abspath_or_url;
   svn_revnum_t peg_revnum = SVN_INVALID_REVNUM;
   svn_revnum_t start_revnum, end_revnum;
   svn_revnum_t youngest_rev = SVN_INVALID_REVNUM;
-  apr_array_header_t *revs;
-  apr_hash_t *rev_locs;
   apr_pool_t *subpool = svn_pool_create(pool);
 
   /* Ensure that we are given some real revision data to work with.
@@ -739,102 +787,48 @@ svn_client__repos_locations(const char *
                                             ra_session, end, pool));
 
   /* Set the output revision variables. */
-  *start_revision = apr_pcalloc(pool, sizeof(**start_revision));
-  (*start_revision)->kind = svn_opt_revision_number;
-  (*start_revision)->value.number = start_revnum;
-  if (end->kind != svn_opt_revision_unspecified)
+  if (start_revision)
     {
-      *end_revision = apr_pcalloc(pool, sizeof(**end_revision));
-      (*end_revision)->kind = svn_opt_revision_number;
-      (*end_revision)->value.number = end_revnum;
+      *start_revision = start_revnum;
     }
-
-  if (start_revnum == peg_revnum && end_revnum == peg_revnum)
+  if (end_revision && end->kind != svn_opt_revision_unspecified)
     {
-      /* Avoid a network request in the common easy case. */
-      *start_url = url;
-      if (end->kind != svn_opt_revision_unspecified)
-        *end_url = url;
-      svn_pool_destroy(subpool);
-      return SVN_NO_ERROR;
+      *end_revision = end_revnum;
     }
 
-  SVN_ERR(svn_ra_get_repos_root2(ra_session, &repos_url, subpool));
-
-  revs = apr_array_make(subpool, 2, sizeof(svn_revnum_t));
-  APR_ARRAY_PUSH(revs, svn_revnum_t) = start_revnum;
-  if (end_revnum != start_revnum)
-    APR_ARRAY_PUSH(revs, svn_revnum_t) = end_revnum;
-
-  SVN_ERR(svn_ra_get_locations(ra_session, &rev_locs, "", peg_revnum,
-                               revs, subpool));
-
-  /* We'd better have all the paths we were looking for! */
-  start_path = apr_hash_get(rev_locs, &start_revnum, sizeof(svn_revnum_t));
-  if (! start_path)
-    return svn_error_createf
-      (SVN_ERR_CLIENT_UNRELATED_RESOURCES, NULL,
-       _("Unable to find repository location for '%s' in revision %ld"),
-       path_or_url_local_style(path, pool), start_revnum);
-
-  end_path = apr_hash_get(rev_locs, &end_revnum, sizeof(svn_revnum_t));
-  if (! end_path)
-    return svn_error_createf
-      (SVN_ERR_CLIENT_UNRELATED_RESOURCES, NULL,
-       _("The location for '%s' for revision %ld does not exist in the "
-         "repository or refers to an unrelated object"),
-       path_or_url_local_style(path, pool), end_revnum);
-
-  /* Set our return variables */
-  *start_url = svn_path_url_add_component2(repos_url, start_path + 1, pool);
-  if (end->kind != svn_opt_revision_unspecified)
-    *end_url = svn_path_url_add_component2(repos_url, end_path + 1, pool);
-
+  SVN_ERR(repos_locations(start_url, end_url,
+                          ra_session, url, peg_revnum,
+                          start_revnum, end_revnum,
+                          pool, subpool));
   svn_pool_destroy(subpool);
   return SVN_NO_ERROR;
 }
 
 
 svn_error_t *
-svn_client__get_youngest_common_ancestor(const char **ancestor_path,
+svn_client__get_youngest_common_ancestor(const char **ancestor_relpath,
+                                         const char **ancestor_url,
                                          svn_revnum_t *ancestor_revision,
-                                         const char *path_or_url1,
+                                         const char *url1,
                                          svn_revnum_t rev1,
-                                         const char *path_or_url2,
+                                         const char *url2,
                                          svn_revnum_t rev2,
                                          svn_client_ctx_t *ctx,
                                          apr_pool_t *pool)
 {
   apr_pool_t *sesspool = svn_pool_create(pool);
-  svn_ra_session_t *session1, *session2;
+  svn_ra_session_t *session;
+  const char *repos_root_url;
   apr_hash_t *history1, *history2;
   apr_hash_index_t *hi;
   svn_revnum_t yc_revision = SVN_INVALID_REVNUM;
-  const char *yc_path = NULL;
-  svn_opt_revision_t revision1, revision2;
+  const char *yc_relpath = NULL;
   svn_boolean_t has_rev_zero_history1;
   svn_boolean_t has_rev_zero_history2;
 
-  revision1.kind = revision2.kind = svn_opt_revision_number;
-  revision1.value.number = rev1;
-  revision2.value.number = rev2;
-
-  /* Open RA sessions for the two locations.
-   * ### TODO: As they are assumed to be in the same repository, we
-   * should share a single session, tracking the two URLs separately. */
-  {
-    svn_revnum_t peg;
-    const char *url;
-
-    SVN_ERR(svn_client__ra_session_from_path(&session1, &peg, &url,
-                                             path_or_url1, NULL,
-                                             &revision1, &revision1,
-                                             ctx, pool));
-    SVN_ERR(svn_client__ra_session_from_path(&session2, &peg, &url,
-                                             path_or_url2, NULL,
-                                             &revision2, &revision2,
-                                             ctx, pool));
-  }
+  /* Open an RA session for the two locations. */
+  SVN_ERR(svn_client_open_ra_session(&session, url1, ctx, sesspool));
+  SVN_ERR(svn_ra_get_repos_root2(session, &repos_root_url, pool));
 
   /* We're going to cheat and use history-as-mergeinfo because it
      saves us a bunch of annoying custom data comparisons and such. */
@@ -843,14 +837,15 @@ svn_client__get_youngest_common_ancestor
                                                rev1,
                                                SVN_INVALID_REVNUM,
                                                SVN_INVALID_REVNUM,
-                                               session1, ctx, pool));
+                                               session, ctx, pool));
+  SVN_ERR(svn_ra_reparent(session, url2, pool));
   SVN_ERR(svn_client__get_history_as_mergeinfo(&history2,
                                                &has_rev_zero_history2,
                                                rev2,
                                                SVN_INVALID_REVNUM,
                                                SVN_INVALID_REVNUM,
-                                               session2, ctx, pool));
-
+                                               session, ctx, pool));
+  /* Close the source and target sessions. */
   svn_pool_destroy(sesspool);
 
   /* Loop through the first location's history, check for overlapping
@@ -878,7 +873,7 @@ svn_client__get_youngest_common_ancestor
                   || (yc_range->end > yc_revision))
                 {
                   yc_revision = yc_range->end;
-                  yc_path = path + 1;
+                  yc_relpath = path + 1;
                 }
             }
         }
@@ -886,13 +881,52 @@ svn_client__get_youngest_common_ancestor
 
   /* It's possible that PATH_OR_URL1 and PATH_OR_URL2's only common
      history is revision 0. */
-  if (!yc_path && has_rev_zero_history1 && has_rev_zero_history2)
+  if (!yc_relpath && has_rev_zero_history1 && has_rev_zero_history2)
     {
-      yc_path = "/";
+      yc_relpath = "";
       yc_revision = 0;
     }
 
-  *ancestor_path = yc_path;
-  *ancestor_revision = yc_revision;
+  if (ancestor_relpath)
+    *ancestor_relpath = yc_relpath;
+  if (ancestor_url)
+    *ancestor_url
+      = yc_relpath ? svn_path_url_add_component2(repos_root_url, yc_relpath,
+                                                 pool) : NULL;
+  if (ancestor_revision)
+    *ancestor_revision = yc_revision;
+  return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_client__youngest_common_ancestor(const char **ancestor_url,
+                                     svn_revnum_t *ancestor_rev,
+                                     const char *path_or_url1,
+                                     const svn_opt_revision_t *revision1,
+                                     const char *path_or_url2,
+                                     const svn_opt_revision_t *revision2,
+                                     svn_client_ctx_t *ctx,
+                                     apr_pool_t *result_pool,
+                                     apr_pool_t *scratch_pool)
+{
+  apr_pool_t *sesspool = svn_pool_create(scratch_pool);
+  svn_ra_session_t *session;
+  const char *url1, *url2;
+  svn_revnum_t rev1, rev2;
+
+  /* Resolve the two locations */
+  SVN_ERR(svn_client__ra_session_from_path(&session, &rev1, &url1,
+                                           path_or_url1, NULL,
+                                           revision1, revision1,
+                                           ctx, sesspool));
+  SVN_ERR(resolve_rev_and_url(&rev2, &url2, session,
+                              path_or_url2, revision2, revision2,
+                              ctx, scratch_pool));
+
+  SVN_ERR(svn_client__get_youngest_common_ancestor(
+            NULL, ancestor_url, ancestor_rev,
+            url1, rev1, url2, rev2, ctx, result_pool));
+
+  svn_pool_destroy(sesspool);
   return SVN_NO_ERROR;
 }