You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@subversion.apache.org by pr...@apache.org on 2013/06/05 11:22:51 UTC

svn commit: r1489765 [6/22] - in /subversion/branches/verify-keep-going: ./ build/ build/ac-macros/ build/generator/ build/generator/templates/ contrib/hook-scripts/ contrib/server-side/fsfsfixer/ contrib/server-side/fsfsfixer/fixer/ notes/ subversion/...

Modified: subversion/branches/verify-keep-going/subversion/libsvn_client/export.c
URL: http://svn.apache.org/viewvc/subversion/branches/verify-keep-going/subversion/libsvn_client/export.c?rev=1489765&r1=1489764&r2=1489765&view=diff
==============================================================================
--- subversion/branches/verify-keep-going/subversion/libsvn_client/export.c (original)
+++ subversion/branches/verify-keep-going/subversion/libsvn_client/export.c Wed Jun  5 09:22:43 2013
@@ -193,7 +193,7 @@ export_node(void *baton,
   svn_stream_t *dst_stream;
   const char *dst_tmp;
   svn_error_t *err;
-  
+
   const char *to_abspath = svn_dirent_join(
                                 eib->to_path,
                                 svn_dirent_skip_ancestor(eib->origin_abspath,
@@ -392,10 +392,11 @@ export_node(void *baton,
           suffix = "";
         }
 
-      SVN_ERR(svn_subst_build_keywords2
-              (&kw, keywords->data,
-               apr_psprintf(scratch_pool, "%ld%s", changed_rev, suffix),
-               url, tm, author, scratch_pool));
+      SVN_ERR(svn_subst_build_keywords3(&kw, keywords->data,
+                                        apr_psprintf(scratch_pool, "%ld%s",
+                                                     changed_rev, suffix),
+                                        url, status->repos_root_url, tm,
+                                        author, scratch_pool));
     }
 
   /* For atomicity, we translate to a tmp file and then rename the tmp file
@@ -495,6 +496,7 @@ open_root_internal(const char *path,
 
 struct edit_baton
 {
+  const char *repos_root_url;
   const char *root_path;
   const char *root_url;
   svn_boolean_t force;
@@ -541,6 +543,7 @@ struct file_baton
   /* Any keyword vals to be substituted */
   const char *revision;
   const char *url;
+  const char *repos_root_url;
   const char *author;
   apr_time_t date;
 
@@ -662,6 +665,7 @@ add_file(const char *path,
   fb->edit_baton = eb;
   fb->path = full_path;
   fb->url = full_url;
+  fb->repos_root_url = eb->repos_root_url;
   fb->pool = pool;
 
   *baton = fb;
@@ -827,8 +831,9 @@ close_file(void *file_baton,
         }
 
       if (fb->keywords_val)
-        SVN_ERR(svn_subst_build_keywords2(&final_kw, fb->keywords_val->data,
-                                          fb->revision, fb->url, fb->date,
+        SVN_ERR(svn_subst_build_keywords3(&final_kw, fb->keywords_val->data,
+                                          fb->revision, fb->url,
+                                          fb->repos_root_url, fb->date,
                                           fb->author, pool));
 
       SVN_ERR(svn_subst_copy_and_translate4(fb->tmppath, fb->path,
@@ -898,7 +903,7 @@ get_editor_ev1(const svn_delta_editor_t 
                apr_pool_t *scratch_pool)
 {
   svn_delta_editor_t *editor = svn_delta_default_editor(result_pool);
-  
+
   editor->set_target_revision = set_target_revision;
   editor->open_root = open_root;
   editor->add_directory = add_directory;
@@ -948,7 +953,7 @@ add_file_ev2(void *baton,
   /* Any keyword vals to be substituted */
   const char *revision = NULL;
   const char *author = NULL;
-  apr_time_t date = 0; 
+  apr_time_t date = 0;
 
   /* Look at any properties for additional information. */
   if ( (val = svn_hash_gets(props, SVN_PROP_EOL_STYLE)) )
@@ -959,14 +964,14 @@ add_file_ev2(void *baton,
 
   if ( (val = svn_hash_gets(props, SVN_PROP_EXECUTABLE)) )
     executable_val = val;
-  
+
   /* Try to fill out the baton's keywords-structure too. */
   if ( (val = svn_hash_gets(props, SVN_PROP_ENTRY_COMMITTED_REV)) )
     revision = val->data;
 
   if ( (val = svn_hash_gets(props, SVN_PROP_ENTRY_COMMITTED_DATE)) )
     SVN_ERR(svn_time_from_cstring(&date, val->data, scratch_pool));
-  
+
   if ( (val = svn_hash_gets(props, SVN_PROP_ENTRY_LAST_AUTHOR)) )
     author = val->data;
 
@@ -1012,9 +1017,10 @@ add_file_ev2(void *baton,
             }
 
           if (keywords_val)
-            SVN_ERR(svn_subst_build_keywords2(&final_kw, keywords_val->data,
-                                              revision, full_url, date,
-                                              author, scratch_pool));
+            SVN_ERR(svn_subst_build_keywords3(&final_kw, keywords_val->data,
+                                              revision, full_url,
+                                              eb->repos_root_url,
+                                              date, author, scratch_pool));
 
           /* Writing through a translated stream is more efficient than
              reading through one, so we wrap TMP_STREAM and not CONTENTS. */
@@ -1076,7 +1082,7 @@ add_directory_ev2(void *baton,
 
   if ( (val = svn_hash_gets(props, SVN_PROP_EXTERNALS)) )
     SVN_ERR(add_externals(eb->externals, full_path, val));
-  
+
   if (eb->notify_func)
     {
       svn_wc_notify_t *notify = svn_wc_create_notify(full_path,
@@ -1247,6 +1253,7 @@ export_file(const char *from_path_or_url
   fb->path = eb->root_path;
   fb->url = eb->root_url;
   fb->pool = scratch_pool;
+  fb->repos_root_url = eb->repos_root_url;
 
   /* Copied from apply_textdelta(). */
   SVN_ERR(svn_stream_open_unique(&fb->tmp_stream, &fb->tmppath,
@@ -1342,15 +1349,12 @@ export_directory(const char *from_path_o
 
   if (! ignore_externals && depth == svn_depth_infinity)
     {
-      const char *repos_root_url;
       const char *to_abspath;
 
-      SVN_ERR(svn_ra_get_repos_root2(ra_session, &repos_root_url,
-                                     scratch_pool));
       SVN_ERR(svn_dirent_get_absolute(&to_abspath, to_path, scratch_pool));
       SVN_ERR(svn_client__export_externals(eb->externals,
                                            from_path_or_url,
-                                           to_abspath, repos_root_url,
+                                           to_abspath, eb->repos_root_url,
                                            depth, native_eol,
                                            ignore_keywords,
                                            ctx, scratch_pool));
@@ -1404,6 +1408,7 @@ svn_client_export5(svn_revnum_t *result_
                                                 peg_revision,
                                                 revision, ctx, pool));
 
+      SVN_ERR(svn_ra_get_repos_root2(ra_session, &eb->repos_root_url, pool));
       eb->root_path = to_path;
       eb->root_url = loc->url;
       eb->force = overwrite;

Modified: subversion/branches/verify-keep-going/subversion/libsvn_client/externals.c
URL: http://svn.apache.org/viewvc/subversion/branches/verify-keep-going/subversion/libsvn_client/externals.c?rev=1489765&r1=1489764&r2=1489765&view=diff
==============================================================================
--- subversion/branches/verify-keep-going/subversion/libsvn_client/externals.c (original)
+++ subversion/branches/verify-keep-going/subversion/libsvn_client/externals.c Wed Jun  5 09:22:43 2013
@@ -24,7 +24,7 @@
 /* ==================================================================== */
 
 
- 
+
 /*** Includes. ***/
 
 #include <apr_uri.h>
@@ -42,7 +42,7 @@
 
 #include "svn_private_config.h"
 #include "private/svn_wc_private.h"
- 
+
 
 /* Remove the directory at LOCAL_ABSPATH from revision control, and do the
  * same to any revision controlled directories underneath LOCAL_ABSPATH

Modified: subversion/branches/verify-keep-going/subversion/libsvn_client/import.c
URL: http://svn.apache.org/viewvc/subversion/branches/verify-keep-going/subversion/libsvn_client/import.c?rev=1489765&r1=1489764&r2=1489765&view=diff
==============================================================================
--- subversion/branches/verify-keep-going/subversion/libsvn_client/import.c (original)
+++ subversion/branches/verify-keep-going/subversion/libsvn_client/import.c Wed Jun  5 09:22:43 2013
@@ -134,9 +134,9 @@ send_file_contents(const char *local_abs
     }
 
   if (keywords_val)
-    SVN_ERR(svn_subst_build_keywords2(&keywords, keywords_val->data,
+    SVN_ERR(svn_subst_build_keywords3(&keywords, keywords_val->data,
                                       APR_STRINGIFY(SVN_INVALID_REVNUM),
-                                      "", 0, "", pool));
+                                      "", "", 0, "", pool));
   else
     keywords = NULL;
 
@@ -593,6 +593,9 @@ import_dir(const svn_delta_editor_t *edi
  * DEPTH is the depth at which to import PATH; it behaves as for
  * svn_client_import4().
  *
+ * BASE_REV is the revision to use for the root of the commit. We
+ * checked the preconditions against this revision.
+ *
  * NEW_ENTRIES is an ordered array of path components that must be
  * created in the repository (where the ordering direction is
  * parent-to-child).  If PATH is a directory, NEW_ENTRIES may be empty
@@ -641,6 +644,7 @@ import(const char *local_abspath,
        const svn_delta_editor_t *editor,
        void *edit_baton,
        svn_depth_t depth,
+       svn_revnum_t base_rev,
        apr_hash_t *excludes,
        apr_hash_t *autoprops,
        apr_array_header_t *local_ignores,
@@ -662,14 +666,13 @@ import(const char *local_abspath,
   import_ctx->autoprops = autoprops;
   svn_magic__init(&import_ctx->magic_cookie, pool);
 
-  /* Get a root dir baton.  We pass an invalid revnum to open_root
-     to mean "base this on the youngest revision".  Should we have an
-     SVN_YOUNGEST_REVNUM defined for these purposes? */
-  SVN_ERR(editor->open_root(edit_baton, SVN_INVALID_REVNUM,
-                            pool, &root_baton));
+  /* Get a root dir baton.  We pass the revnum we used for testing our
+     assumptions and obtaining inherited properties. */
+  SVN_ERR(editor->open_root(edit_baton, base_rev, pool, &root_baton));
 
   /* Import a file or a directory tree. */
-  SVN_ERR(svn_io_stat_dirent(&dirent, local_abspath, FALSE, pool, pool));
+  SVN_ERR(svn_io_stat_dirent2(&dirent, local_abspath, FALSE, FALSE,
+                              pool, pool));
 
   /* Make the intermediate directory components necessary for properly
      rooting our import source tree.  */
@@ -808,6 +811,9 @@ svn_client_import5(const char *path,
   apr_hash_t *autoprops = NULL;
   apr_array_header_t *global_ignores;
   apr_array_header_t *local_ignores_arr;
+  svn_revnum_t base_rev;
+  apr_array_header_t *inherited_props = NULL;
+  apr_hash_t *url_props = NULL;
 
   if (svn_path_is_url(path))
     return svn_error_createf(SVN_ERR_ILLEGAL_TARGET, NULL,
@@ -848,10 +854,11 @@ svn_client_import5(const char *path,
   SVN_ERR(svn_client_open_ra_session2(&ra_session, url, NULL,
                                       ctx, scratch_pool, iterpool));
 
+  SVN_ERR(svn_ra_get_latest_revnum(ra_session, &base_rev, iterpool));
+
   /* Figure out all the path components we need to create just to have
      a place to stick our imported tree. */
-  SVN_ERR(svn_ra_check_path(ra_session, "", SVN_INVALID_REVNUM, &kind,
-                            iterpool));
+  SVN_ERR(svn_ra_check_path(ra_session, "", base_rev, &kind, iterpool));
 
   /* We can import into directories, but if a file already exists, that's
      an error. */
@@ -870,8 +877,7 @@ svn_client_import5(const char *path,
       APR_ARRAY_PUSH(new_entries, const char *) = dir;
       SVN_ERR(svn_ra_reparent(ra_session, url, iterpool));
 
-      SVN_ERR(svn_ra_check_path(ra_session, "", SVN_INVALID_REVNUM, &kind,
-                                iterpool));
+      SVN_ERR(svn_ra_check_path(ra_session, "", base_rev, &kind, iterpool));
     }
 
   /* Reverse the order of the components we added to our NEW_ENTRIES array. */
@@ -894,6 +900,17 @@ svn_client_import5(const char *path,
   SVN_ERR(svn_client__ensure_revprop_table(&commit_revprops, revprop_table,
                                            log_msg, ctx, scratch_pool));
 
+  /* Obtain properties before opening the commit editor, as at that point we are
+     not allowed to use the existing ra-session */
+  if (! no_ignore /*|| ! no_autoprops*/)
+    {
+      SVN_ERR(svn_ra_get_dir2(ra_session, NULL, NULL, &url_props, "",
+                              base_rev, SVN_DIRENT_KIND, scratch_pool));
+
+      SVN_ERR(svn_ra_get_inherited_props(ra_session, &inherited_props, "", base_rev,
+                                         scratch_pool, iterpool));
+    }
+
   /* Fetch RA commit editor. */
   SVN_ERR(svn_ra__register_editor_shim_callbacks(ra_session,
                         svn_client__get_shim_callbacks(ctx->wc_ctx,
@@ -906,8 +923,13 @@ svn_client_import5(const char *path,
   /* Get inherited svn:auto-props, svn:global-ignores, and
      svn:ignores for the location we are importing to. */
   if (!no_autoprops)
-    SVN_ERR(svn_client__get_all_auto_props(&autoprops, url, ctx,
-                                           scratch_pool, iterpool));
+    {
+      /* ### This should use inherited_props and url_props to avoid creating
+             another ra session to obtain the same values, but using a possibly
+             different HEAD revision */
+      SVN_ERR(svn_client__get_all_auto_props(&autoprops, url, ctx,
+                                             scratch_pool, iterpool));
+    }
   if (no_ignore)
     {
       global_ignores = NULL;
@@ -915,38 +937,48 @@ svn_client_import5(const char *path,
     }
   else
     {
-      svn_opt_revision_t rev;
       apr_array_header_t *config_ignores;
-      apr_hash_t *local_ignores_hash;
+      svn_string_t *val;
+      int i;
+
+      global_ignores = apr_array_make(scratch_pool, 64, sizeof(const char *));
 
-      SVN_ERR(svn_client__get_inherited_ignores(&global_ignores, url, ctx,
-                                                scratch_pool, iterpool));
       SVN_ERR(svn_wc_get_default_ignores(&config_ignores, ctx->config,
                                          scratch_pool));
       global_ignores = apr_array_append(scratch_pool, global_ignores,
                                         config_ignores);
 
-      rev.kind = svn_opt_revision_head;
-      SVN_ERR(svn_client_propget5(&local_ignores_hash, NULL, SVN_PROP_IGNORE, url,
-                                  &rev, &rev, NULL, svn_depth_empty, NULL, ctx,
-                                  scratch_pool, scratch_pool));
+      val = svn_hash_gets(url_props, SVN_PROP_INHERITABLE_IGNORES);
+      if (val)
+        svn_cstring_split_append(global_ignores, val->data, "\n\r\t\v ",
+                                 FALSE, scratch_pool);
+
+      for (i = 0; i < inherited_props->nelts; i++)
+        {
+          svn_prop_inherited_item_t *elt = APR_ARRAY_IDX(
+            inherited_props, i, svn_prop_inherited_item_t *);
+
+          val = svn_hash_gets(elt->prop_hash, SVN_PROP_INHERITABLE_IGNORES);
+
+          if (val)
+            svn_cstring_split_append(global_ignores, val->data, "\n\r\t\v ",
+                                     FALSE, scratch_pool);
+        }
       local_ignores_arr = apr_array_make(scratch_pool, 1, sizeof(const char *));
 
-      if (apr_hash_count(local_ignores_hash))
+      val = svn_hash_gets(url_props, SVN_PROP_IGNORE);
+
+      if (val)
         {
-          svn_string_t *propval = svn_hash_gets(local_ignores_hash, url);
-          if (propval)
-            {
-              svn_cstring_split_append(local_ignores_arr, propval->data,
-                                       "\n\r\t\v ", FALSE, scratch_pool);
-            }
+          svn_cstring_split_append(local_ignores_arr, val->data,
+                                   "\n\r\t\v ", FALSE, scratch_pool);
         }
     }
 
   /* If an error occurred during the commit, abort the edit and return
      the error.  We don't even care if the abort itself fails.  */
   if ((err = import(local_abspath, new_entries, editor, edit_baton,
-                    depth, excludes, autoprops, local_ignores_arr,
+                    depth, base_rev, excludes, autoprops, local_ignores_arr,
                     global_ignores, no_ignore, no_autoprops,
                     ignore_unknown_node_types, filter_callback,
                     filter_baton, ctx, iterpool)))

Modified: subversion/branches/verify-keep-going/subversion/libsvn_client/iprops.c
URL: http://svn.apache.org/viewvc/subversion/branches/verify-keep-going/subversion/libsvn_client/iprops.c?rev=1489765&r1=1489764&r2=1489765&view=diff
==============================================================================
--- subversion/branches/verify-keep-going/subversion/libsvn_client/iprops.c (original)
+++ subversion/branches/verify-keep-going/subversion/libsvn_client/iprops.c Wed Jun  5 09:22:43 2013
@@ -118,7 +118,7 @@ svn_client__iprop_relpaths_to_urls(apr_a
         {
           elt->path_or_url = svn_path_url_add_component2(repos_root_url,
                                                          elt->path_or_url,
-                                                         result_pool);      
+                                                         result_pool);
         }
     }
   return SVN_NO_ERROR;

Modified: subversion/branches/verify-keep-going/subversion/libsvn_client/list.c
URL: http://svn.apache.org/viewvc/subversion/branches/verify-keep-going/subversion/libsvn_client/list.c?rev=1489765&r1=1489764&r2=1489765&view=diff
==============================================================================
--- subversion/branches/verify-keep-going/subversion/libsvn_client/list.c (original)
+++ subversion/branches/verify-keep-going/subversion/libsvn_client/list.c Wed Jun  5 09:22:43 2013
@@ -38,8 +38,8 @@
 #include "svn_private_config.h"
 
 /* Prototypes for referencing before declaration */
-static svn_error_t * 
-list_externals(apr_hash_t *externals, 
+static svn_error_t *
+list_externals(apr_hash_t *externals,
                svn_depth_t depth,
                apr_uint32_t dirent_fields,
                svn_boolean_t fetch_locks,
@@ -78,9 +78,9 @@ list_internal(const char *path_or_url,
    objects and FS_PATH is the absolute filesystem path of the RA session.
    Use SCRATCH_POOL for temporary allocations.
 
-   If the caller passes EXTERNALS as non-NULL, populate the EXTERNALS 
+   If the caller passes EXTERNALS as non-NULL, populate the EXTERNALS
    hash table whose keys are URLs of the directory which has externals
-   definitions, and whose values are the externals description text. 
+   definitions, and whose values are the externals description text.
    Allocate the hash's keys and values in RESULT_POOL.
 
    EXTERNAL_PARENT_URL and EXTERNAL_TARGET are set when external items
@@ -113,13 +113,13 @@ get_dir_contents(apr_uint32_t dirent_fie
 
   if (depth == svn_depth_empty)
     return SVN_NO_ERROR;
-  
+
   /* Get the directory's entries. If externals hash is non-NULL, get its
      properties also. Ignore any not-authorized errors.  */
-  err = svn_ra_get_dir2(ra_session, &tmpdirents, NULL, 
+  err = svn_ra_get_dir2(ra_session, &tmpdirents, NULL,
                         externals ? &prop_hash : NULL,
                         dir, rev, dirent_fields, scratch_pool);
-      
+
   if (err && ((err->apr_err == SVN_ERR_RA_NOT_AUTHORIZED) ||
               (err->apr_err == SVN_ERR_RA_DAV_FORBIDDEN)))
     {
@@ -127,16 +127,16 @@ get_dir_contents(apr_uint32_t dirent_fie
       return SVN_NO_ERROR;
     }
   SVN_ERR(err);
- 
- /* Filter out svn:externals from all properties hash. */ 
-  if (prop_hash) 
+
+ /* Filter out svn:externals from all properties hash. */
+  if (prop_hash)
     prop_val = svn_hash_gets(prop_hash, SVN_PROP_EXTERNALS);
   if (prop_val)
     {
       const char *url;
 
       SVN_ERR(svn_ra_get_session_url(ra_session, &url, scratch_pool));
-      
+
       svn_hash_sets(externals,
                     svn_path_url_add_component2(url, dir, result_pool),
                     svn_string_dup(prop_val, result_pool));
@@ -146,7 +146,7 @@ get_dir_contents(apr_uint32_t dirent_fie
     SVN_ERR(ctx->cancel_func(ctx->cancel_baton));
 
   /* Sort the hash, so we can call the callback in a "deterministic" order. */
-  array = svn_sort__hash(tmpdirents, svn_sort_compare_items_lexically, 
+  array = svn_sort__hash(tmpdirents, svn_sort_compare_items_lexically,
                          scratch_pool);
   for (i = 0; i < array->nelts; ++i)
     {
@@ -172,12 +172,12 @@ get_dir_contents(apr_uint32_t dirent_fie
           || depth == svn_depth_infinity)
         SVN_ERR(list_func(baton, path, the_ent, lock, fs_path,
                           external_parent_url, external_target, iterpool));
-      
-      /* If externals is non-NULL, populate the externals hash table 
+
+      /* If externals is non-NULL, populate the externals hash table
          recursively for all directory entries. */
       if (depth == svn_depth_infinity && the_ent->kind == svn_node_dir)
         SVN_ERR(get_dir_contents(dirent_fields, path, rev,
-                                 ra_session, locks, fs_path, depth, ctx, 
+                                 ra_session, locks, fs_path, depth, ctx,
                                  externals, external_parent_url,
                                  external_target, list_func, baton,
                                  result_pool, iterpool));
@@ -297,36 +297,36 @@ svn_client__ra_stat_compatible(svn_ra_se
 }
 
 /* List the file/directory entries for PATH_OR_URL at REVISION.
-   The actual node revision selected is determined by the path as 
-   it exists in PEG_REVISION.  
-   
-   If DEPTH is svn_depth_infinity, then list all file and directory entries 
-   recursively.  Else if DEPTH is svn_depth_files, list all files under 
+   The actual node revision selected is determined by the path as
+   it exists in PEG_REVISION.
+
+   If DEPTH is svn_depth_infinity, then list all file and directory entries
+   recursively.  Else if DEPTH is svn_depth_files, list all files under
    PATH_OR_URL (if any), but not subdirectories.  Else if DEPTH is
    svn_depth_immediates, list all files and include immediate
    subdirectories (at svn_depth_empty).  Else if DEPTH is
    svn_depth_empty, just list PATH_OR_URL with none of its entries.
- 
+
    DIRENT_FIELDS controls which fields in the svn_dirent_t's are
    filled in.  To have them totally filled in use SVN_DIRENT_ALL,
    otherwise simply bitwise OR together the combination of SVN_DIRENT_*
    fields you care about.
- 
+
    If FETCH_LOCKS is TRUE, include locks when reporting directory entries.
- 
-   If INCLUDE_EXTERNALS is TRUE, also list all external items 
+
+   If INCLUDE_EXTERNALS is TRUE, also list all external items
    reached by recursion.  DEPTH value passed to the original list target
-   applies for the externals also.  EXTERNAL_PARENT_URL is url of the 
+   applies for the externals also.  EXTERNAL_PARENT_URL is url of the
    directory which has the externals definitions.  EXTERNAL_TARGET is the
    target subdirectory of externals definitions.
 
-   Report directory entries by invoking LIST_FUNC/BATON. 
+   Report directory entries by invoking LIST_FUNC/BATON.
    Pass EXTERNAL_PARENT_URL and EXTERNAL_TARGET to LIST_FUNC when external
    items are listed, otherwise both are set to NULL.
- 
+
    Use authentication baton cached in CTX to authenticate against the
    repository.
- 
+
    Use POOL for all allocations.
 */
 static svn_error_t *
@@ -397,7 +397,7 @@ list_internal(const char *path_or_url,
   /* Report the dirent for the target. */
   SVN_ERR(list_func(baton, "", dirent, locks
                     ? (svn_hash_gets(locks, fs_path))
-                    : NULL, fs_path, external_parent_url, 
+                    : NULL, fs_path, external_parent_url,
                     external_target, pool));
 
   if (dirent->kind == svn_node_dir
@@ -405,22 +405,22 @@ list_internal(const char *path_or_url,
           || depth == svn_depth_immediates
           || depth == svn_depth_infinity))
     SVN_ERR(get_dir_contents(dirent_fields, "", loc->rev, ra_session, locks,
-                             fs_path, depth, ctx, externals, 
+                             fs_path, depth, ctx, externals,
                              external_parent_url, external_target, list_func,
                              baton, pool, pool));
-  
+
   /* We handle externals after listing entries under path_or_url, so that
      handling external items (and any errors therefrom) doesn't delay
      the primary operation. */
   if (include_externals && apr_hash_count(externals))
     {
-      /* The 'externals' hash populated by get_dir_contents() is processed 
+      /* The 'externals' hash populated by get_dir_contents() is processed
          here. */
-      SVN_ERR(list_externals(externals, depth, dirent_fields, 
+      SVN_ERR(list_externals(externals, depth, dirent_fields,
                              fetch_locks, list_func, baton,
                              ctx, pool));
-    } 
-  
+    }
+
   return SVN_NO_ERROR;
 }
 
@@ -511,7 +511,7 @@ list_external_items(apr_array_header_t *
 /* List external items defined on each external in EXTERNALS, a const char *
    externals_parent_url(url of the directory which has the externals
    definitions) of all externals mapping to the svn_string_t * externals_desc
-   (externals description text). All other options are the same as those 
+   (externals description text). All other options are the same as those
    passed to svn_client_list(). */
 static svn_error_t *
 list_externals(apr_hash_t *externals,

Modified: subversion/branches/verify-keep-going/subversion/libsvn_client/log.c
URL: http://svn.apache.org/viewvc/subversion/branches/verify-keep-going/subversion/libsvn_client/log.c?rev=1489765&r1=1489764&r2=1489765&view=diff
==============================================================================
--- subversion/branches/verify-keep-going/subversion/libsvn_client/log.c (original)
+++ subversion/branches/verify-keep-going/subversion/libsvn_client/log.c Wed Jun  5 09:22:43 2013
@@ -42,6 +42,7 @@
 #include "svn_private_config.h"
 #include "private/svn_wc_private.h"
 
+#include <assert.h>
 
 /*** Getting misc. information ***/
 
@@ -260,57 +261,225 @@ limit_receiver(void *baton, svn_log_entr
   return rb->receiver(rb->baton, log_entry, pool);
 }
 
-
-/*** Public Interface. ***/
+/* Resolve the URLs or WC path in TARGETS as per the svn_client_log5 API.
 
+   The limitations on TARGETS specified by svn_client_log5 are enforced here.
+   So TARGETS can only contain a single WC path or a URL and zero or more
+   relative paths -- anything else will raise an error. 
 
-svn_error_t *
-svn_client_log5(const apr_array_header_t *targets,
-                const svn_opt_revision_t *peg_revision,
-                const apr_array_header_t *revision_ranges,
-                int limit,
-                svn_boolean_t discover_changed_paths,
-                svn_boolean_t strict_node_history,
-                svn_boolean_t include_merged_revisions,
-                const apr_array_header_t *revprops,
-                svn_log_entry_receiver_t real_receiver,
-                void *real_receiver_baton,
-                svn_client_ctx_t *ctx,
-                apr_pool_t *pool)
+   PEG_REVISION, TARGETS, and CTX are as per svn_client_log5.
+
+   If TARGETS contains a single WC path then set *RA_TARGET to the absolute
+   path of that single path if PEG_REVISION is dependent on the working copy
+   (e.g. PREV).  Otherwise set *RA_TARGET to the corresponding URL for the
+   single WC path.  Set *RELATIVE_TARGETS to an array with a single
+   element "".
+
+   If TARGETS contains only a single URL, then set *RA_TARGET to a copy of
+   that URL and *RELATIVE_TARGETS to an array with a single element "".
+
+   If TARGETS contains a single URL and one or more relative paths, then
+   set *RA_TARGET to a copy of that URL and *RELATIVE_TARGETS to a copy of
+   each relative path after the URL.
+
+   If *PEG_REVISION is svn_opt_revision_unspecified, then *PEG_REVISION is
+   set to svn_opt_revision_head for URLs or svn_opt_revision_working for a
+   WC path.
+
+   *RA_TARGET and *RELATIVE_TARGETS are allocated in RESULT_POOL. */
+static svn_error_t *
+resolve_log_targets(apr_array_header_t **relative_targets,
+                    const char **ra_target,
+                    svn_opt_revision_t *peg_revision,
+                    const apr_array_header_t *targets,
+                    svn_client_ctx_t *ctx,
+                    apr_pool_t *result_pool,
+                    apr_pool_t *scratch_pool)
 {
-  svn_ra_session_t *ra_session;
-  const char *url_or_path;
-  svn_boolean_t has_log_revprops;
-  apr_array_header_t *condensed_targets;
-  svn_opt_revision_t session_opt_rev;
-  const char *ra_target;
-  pre_15_receiver_baton_t rb = {0};
-  apr_pool_t *iterpool;
   int i;
-  svn_opt_revision_t peg_rev;
-  svn_boolean_t url_targets = FALSE;
+  svn_boolean_t url_targets;
+
+  /* Per svn_client_log5, TARGETS contains either a URL followed by zero or
+     more relative paths, or one working copy path. */
+  const char *url_or_path = APR_ARRAY_IDX(targets, 0, const char *);
+
+  /* svn_client_log5 requires at least one target. */
+  if (targets->nelts == 0)
+    return svn_error_create(SVN_ERR_ILLEGAL_TARGET, NULL,
+                            _("No valid target found"));
+
+  /* Initialize the output array.  At a minimum, we need room for one
+     (possibly empty) relpath.  Otherwise, we have to hold a relpath
+     for every item in TARGETS except the first.  */
+  *relative_targets = apr_array_make(result_pool,
+                                     MAX(1, targets->nelts - 1),
+                                     sizeof(const char *));
 
-  if (revision_ranges->nelts == 0)
+  if (svn_path_is_url(url_or_path))
     {
-      return svn_error_create
-        (SVN_ERR_CLIENT_BAD_REVISION, NULL,
-         _("Missing required revision specification"));
+      /* An unspecified PEG_REVISION for a URL path defaults
+         to svn_opt_revision_head. */
+      if (peg_revision->kind == svn_opt_revision_unspecified)
+        peg_revision->kind = svn_opt_revision_head;
+
+      /* The logic here is this: If we get passed one argument, we assume
+         it is the full URL to a file/dir we want log info for. If we get
+         a URL plus some paths, then we assume that the URL is the base,
+         and that the paths passed are relative to it.  */
+      if (targets->nelts > 1)
+        {
+          /* We have some paths, let's use them. Start after the URL.  */
+          for (i = 1; i < targets->nelts; i++)
+            {
+              const char *target;
+
+              target = APR_ARRAY_IDX(targets, i, const char *);
+
+              if (svn_path_is_url(target) || svn_dirent_is_absolute(target))
+                return svn_error_createf(SVN_ERR_ILLEGAL_TARGET, NULL,
+                                         _("'%s' is not a relative path"),
+                                          target);
+
+              APR_ARRAY_PUSH(*relative_targets, const char *) =
+                apr_pstrdup(result_pool, target);
+            }
+        }
+      else
+        {
+          /* If we have a single URL, then the session will be rooted at
+             it, so just send an empty string for the paths we are
+             interested in. */
+          APR_ARRAY_PUSH(*relative_targets, const char *) = "";
+        }
+
+      /* Remember that our targets are URLs. */
+      url_targets = TRUE;
     }
+  else /* WC path target. */
+    {
+      const char *target;
+      const char *target_abspath;
 
-  /* Make a copy of PEG_REVISION, we may need to change it to a
-     default value. */
-  peg_rev = *peg_revision;
+      url_targets = FALSE;
+      if (targets->nelts > 1)
+        return svn_error_create(SVN_ERR_UNSUPPORTED_FEATURE, NULL,
+                                _("When specifying working copy paths, only "
+                                  "one target may be given"));
 
-  /* Use the passed URL, if there is one.  */
-  url_or_path = APR_ARRAY_IDX(targets, 0, const char *);
-  session_opt_rev.kind = svn_opt_revision_unspecified;
+      /* An unspecified PEG_REVISION for a working copy path defaults
+         to svn_opt_revision_working. */
+      if (peg_revision->kind == svn_opt_revision_unspecified)
+        peg_revision->kind = svn_opt_revision_working;
 
-  for (i = 0; i < revision_ranges->nelts; i++)
+      /* Get URLs for each target */
+      target = APR_ARRAY_IDX(targets, 0, const char *);
+
+      SVN_ERR(svn_dirent_get_absolute(&target_abspath, target, scratch_pool));
+      SVN_ERR(svn_wc__node_get_url(&url_or_path, ctx->wc_ctx, target_abspath,
+                                   scratch_pool, scratch_pool));
+      APR_ARRAY_PUSH(*relative_targets, const char *) = "";
+    }
+
+  /* If this is a revision type that requires access to the working copy,
+   * we use our initial target path to figure out where to root the RA
+   * session, otherwise we use our URL. */
+  if (SVN_CLIENT__REVKIND_NEEDS_WC(peg_revision->kind))
+    {
+      if (url_targets)
+        return svn_error_create(SVN_ERR_CLIENT_BAD_REVISION, NULL,
+                                _("PREV, BASE, or COMMITTED revision "
+                                  "keywords are invalid for URL"));
+
+      else
+        SVN_ERR(svn_dirent_get_absolute(
+          ra_target, APR_ARRAY_IDX(targets, 0, const char *), result_pool));
+    }
+  else
+    {
+      *ra_target = apr_pstrdup(result_pool, url_or_path);
+    }
+
+  return SVN_NO_ERROR;
+}
+
+/* Keep track of oldest and youngest opt revs found.
+
+   If REV is younger than *YOUNGEST_REV, or *YOUNGEST_REV is
+   svn_opt_revision_unspecified, then set *YOUNGEST_REV equal to REV.
+
+   If REV is older than *OLDEST_REV, or *OLDEST_REV is
+   svn_opt_revision_unspecified, then set *OLDEST_REV equal to REV. */
+static void
+find_youngest_and_oldest_revs(svn_revnum_t *youngest_rev,
+                              svn_revnum_t *oldest_rev,
+                              svn_revnum_t rev)
+{
+  /* Is REV younger than YOUNGEST_REV? */
+  if (! SVN_IS_VALID_REVNUM(*youngest_rev)
+      || rev > *youngest_rev)
+    *youngest_rev = rev;
+
+  if (! SVN_IS_VALID_REVNUM(*oldest_rev)
+      || rev < *oldest_rev)
+    *oldest_rev = rev;
+}
+
+typedef struct rev_range_t
+{
+  svn_revnum_t range_start;
+  svn_revnum_t range_end;
+} rev_range_t;
+
+/* Convert array of svn_opt_revision_t ranges to an array of svn_revnum_t
+   ranges.
+
+   Given a log target URL_OR_ABSPATH@PEG_REV and an array of
+   svn_opt_revision_range_t's OPT_REV_RANGES, resolve the opt revs in
+   OPT_REV_RANGES to svn_revnum_t's and return these in *REVISION_RANGES, an
+   array of rev_range_t *.
+
+   Set *YOUNGEST_REV and *OLDEST_REV to the youngest and oldest revisions
+   found in *REVISION_RANGES.
+
+   If the repository needs to be contacted to resolve svn_opt_revision_date or
+   svn_opt_revision_head revisions, then the session used to do this is
+   RA_SESSION; it must be an open session to any URL in the right repository.
+*/
+static svn_error_t*
+convert_opt_rev_array_to_rev_range_array(
+  apr_array_header_t **revision_ranges,
+  svn_revnum_t *youngest_rev,
+  svn_revnum_t *oldest_rev,
+  svn_ra_session_t *ra_session,
+  const char *url_or_abspath,
+  const apr_array_header_t *opt_rev_ranges,
+  const svn_opt_revision_t *peg_rev,
+  svn_client_ctx_t *ctx,
+  apr_pool_t *result_pool,
+  apr_pool_t *scratch_pool)
+{
+  int i;
+  svn_revnum_t head_rev = SVN_INVALID_REVNUM;
+
+  /* Initialize the input/output parameters. */
+  *youngest_rev = *oldest_rev = SVN_INVALID_REVNUM;
+
+  /* Convert OPT_REV_RANGES to an array of rev_range_t and find the youngest
+     and oldest revision range that spans all of OPT_REV_RANGES. */
+  *revision_ranges = apr_array_make(result_pool, opt_rev_ranges->nelts,
+                                    sizeof(rev_range_t *));
+
+  for (i = 0; i < opt_rev_ranges->nelts; i++)
     {
       svn_opt_revision_range_t *range;
+      rev_range_t *rev_range;
+      svn_boolean_t start_same_as_end = FALSE;
 
-      range = APR_ARRAY_IDX(revision_ranges, i, svn_opt_revision_range_t *);
+      range = APR_ARRAY_IDX(opt_rev_ranges, i, svn_opt_revision_range_t *);
 
+      /* Right now RANGE can be any valid pair of svn_opt_revision_t's.  We
+         will now convert all RANGEs in place to the corresponding
+         svn_opt_revision_number kind. */
       if ((range->start.kind != svn_opt_revision_unspecified)
           && (range->end.kind == svn_opt_revision_unspecified))
         {
@@ -329,15 +498,15 @@ svn_client_log5(const apr_array_header_t
           /* Default to any specified peg revision.  Otherwise, if the
            * first target is a URL, then we default to HEAD:0.  Lastly,
            * the default is BASE:0 since WC@HEAD may not exist. */
-          if (peg_rev.kind == svn_opt_revision_unspecified)
+          if (peg_rev->kind == svn_opt_revision_unspecified)
             {
-              if (svn_path_is_url(url_or_path))
+              if (svn_path_is_url(url_or_abspath))
                 range->start.kind = svn_opt_revision_head;
               else
                 range->start.kind = svn_opt_revision_base;
             }
           else
-            range->start = peg_rev;
+            range->start = *peg_rev;
 
           if (range->end.kind == svn_opt_revision_unspecified)
             {
@@ -354,163 +523,117 @@ svn_client_log5(const apr_array_header_t
              _("Missing required revision specification"));
         }
 
-      /* Determine the revision to open the RA session to. */
-      if (session_opt_rev.kind == svn_opt_revision_unspecified)
+      /* Does RANGE describe a single svn_opt_revision_t? */
+      if (range->start.kind == range->end.kind)
         {
-          if (range->start.kind == svn_opt_revision_number &&
-              range->end.kind == svn_opt_revision_number)
+          if (range->start.kind == svn_opt_revision_number)
             {
-              session_opt_rev =
-                  (range->start.value.number > range->end.value.number ?
-                   range->start : range->end);
+              if (range->start.value.number == range->end.value.number)
+                start_same_as_end = TRUE;
             }
-          else if (range->start.kind == svn_opt_revision_head ||
-                   range->end.kind == svn_opt_revision_head)
+          else if (range->start.kind == svn_opt_revision_date)
             {
-              session_opt_rev.kind = svn_opt_revision_head;
+              if (range->start.value.date == range->end.value.date)
+                start_same_as_end = TRUE;
             }
-          else if (range->start.kind == svn_opt_revision_date &&
-                   range->end.kind == svn_opt_revision_date)
+          else
             {
-              session_opt_rev =
-                  (range->start.value.date > range->end.value.date ?
-                   range->start : range->end);
+              start_same_as_end = TRUE;
             }
         }
-    }
-
-  /* Use the passed URL, if there is one.  */
-  if (svn_path_is_url(url_or_path))
-    {
-      /* Initialize this array, since we'll be building it below */
-      condensed_targets = apr_array_make(pool, 1, sizeof(const char *));
-
-      /* The logic here is this: If we get passed one argument, we assume
-         it is the full URL to a file/dir we want log info for. If we get
-         a URL plus some paths, then we assume that the URL is the base,
-         and that the paths passed are relative to it.  */
-      if (targets->nelts > 1)
-        {
-          /* We have some paths, let's use them. Start after the URL.  */
-          for (i = 1; i < targets->nelts; i++)
-            {
-              const char *target;
-
-              target = APR_ARRAY_IDX(targets, i, const char *);
 
-              if (svn_path_is_url(target) || svn_dirent_is_absolute(target))
-                return svn_error_createf(SVN_ERR_ILLEGAL_TARGET, NULL,
-                                         _("'%s' is not a relative path"),
-                                          target);
-
-              APR_ARRAY_PUSH(condensed_targets, const char *) = target;
-            }
-        }
+      rev_range = apr_palloc(result_pool, sizeof(*rev_range));
+      SVN_ERR(svn_client__get_revision_number(
+                &rev_range->range_start, &head_rev,
+                ctx->wc_ctx, url_or_abspath, ra_session,
+                &range->start, scratch_pool));
+      if (start_same_as_end)
+        rev_range->range_end = rev_range->range_start;
       else
-        {
-          /* If we have a single URL, then the session will be rooted at
-             it, so just send an empty string for the paths we are
-             interested in. */
-          APR_ARRAY_PUSH(condensed_targets, const char *) = "";
-        }
-
-      /* Remember that our targets are URLs. */
-      url_targets = TRUE;
+        SVN_ERR(svn_client__get_revision_number(
+                  &rev_range->range_end, &head_rev,
+                  ctx->wc_ctx, url_or_abspath, ra_session,
+                  &range->end, scratch_pool));
+
+      /* Possibly update the oldest and youngest revisions requested. */
+      find_youngest_and_oldest_revs(youngest_rev,
+                                    oldest_rev,
+                                    rev_range->range_start);
+      find_youngest_and_oldest_revs(youngest_rev,
+                                    oldest_rev,
+                                    rev_range->range_end);
+      APR_ARRAY_PUSH(*revision_ranges, rev_range_t *) = rev_range;
     }
-  else
-    {
-      apr_array_header_t *target_urls;
-      apr_array_header_t *real_targets;
 
-      /* See FIXME about multiple wc targets, below. */
-      if (targets->nelts > 1)
-        return svn_error_create(SVN_ERR_UNSUPPORTED_FEATURE, NULL,
-                                _("When specifying working copy paths, only "
-                                  "one target may be given"));
+  return SVN_NO_ERROR;
+}
 
-      /* An unspecified PEG_REVISION for a working copy path defaults
-         to svn_opt_revision_working. */
-      if (peg_rev.kind == svn_opt_revision_unspecified)
-          peg_rev.kind = svn_opt_revision_working;
+static int
+compare_rev_to_segment(const void *key_p,
+                       const void *element_p)
+{
+  svn_revnum_t rev =
+    * (svn_revnum_t *)key_p;
+  const svn_location_segment_t *segment =
+    *((const svn_location_segment_t * const *) element_p);
+
+  if (rev < segment->range_start)
+    return -1;
+  else if (rev > segment->range_end)
+    return 1;
+  else
+    return 0;
+}
 
-      /* Get URLs for each target */
-      target_urls = apr_array_make(pool, 1, sizeof(const char *));
-      real_targets = apr_array_make(pool, 1, sizeof(const char *));
-      iterpool = svn_pool_create(pool);
-      for (i = 0; i < targets->nelts; i++)
-        {
-          const char *url;
-          const char *target = APR_ARRAY_IDX(targets, i, const char *);
-          const char *target_abspath;
-
-          svn_pool_clear(iterpool);
-          SVN_ERR(svn_dirent_get_absolute(&target_abspath, target, iterpool));
-          SVN_ERR(svn_wc__node_get_url(&url, ctx->wc_ctx, target_abspath,
-                                       pool, iterpool));
-
-          if (! url)
-            return svn_error_createf
-              (SVN_ERR_ENTRY_MISSING_URL, NULL,
-               _("Entry '%s' has no URL"),
-               svn_dirent_local_style(target, pool));
-
-          APR_ARRAY_PUSH(target_urls, const char *) = url;
-          APR_ARRAY_PUSH(real_targets, const char *) = target;
-        }
-
-      /* if we have no valid target_urls, just exit. */
-      if (target_urls->nelts == 0)
-        return SVN_NO_ERROR;
-
-      /* Find the base URL and condensed targets relative to it. */
-      SVN_ERR(svn_uri_condense_targets(&url_or_path, &condensed_targets,
-                                       target_urls, TRUE, pool, iterpool));
-
-      if (condensed_targets->nelts == 0)
-        APR_ARRAY_PUSH(condensed_targets, const char *) = "";
-
-      /* 'targets' now becomes 'real_targets', which has bogus,
-         unversioned things removed from it. */
-      targets = real_targets;
-      svn_pool_destroy(iterpool);
-    }
-
-
-  {
-    svn_client__pathrev_t *actual_loc;
-
-    /* If this is a revision type that requires access to the working copy,
-     * we use our initial target path to figure out where to root the RA
-     * session, otherwise we use our URL. */
-    if (SVN_CLIENT__REVKIND_NEEDS_WC(peg_rev.kind))
-      {
-        if (url_targets)
-          SVN_ERR(svn_uri_condense_targets(&ra_target, NULL, targets,
-                                           TRUE, pool, pool));
-        else
-          SVN_ERR(svn_dirent_condense_targets(&ra_target, NULL, targets,
-                                              TRUE, pool, pool));
-      }
-    else
-      ra_target = url_or_path;
-
-    SVN_ERR(svn_client__ra_session_from_path2(&ra_session, &actual_loc,
-                                             ra_target, NULL,
-                                             &peg_rev, &session_opt_rev,
-                                             ctx, pool));
+/* Run svn_ra_get_log2 for PATHS, one or more paths relative to RA_SESSION's
+   common parent, for each revision in REVISION_RANGES, an array of
+   rev_range_t.
+
+   RA_SESSION is an open session pointing to ACTUAL_LOC.
+
+   LOG_SEGMENTS is an array of svn_location_segment_t * items representing the
+   history of PATHS from the oldest to youngest revisions found in
+   REVISION_RANGES.
+
+   The TARGETS, LIMIT, DISCOVER_CHANGED_PATHS, STRICT_NODE_HISTORY,
+   INCLUDE_MERGED_REVISIONS, REVPROPS, REAL_RECEIVER, and REAL_RECEIVER_BATON
+   parameters are all as per the svn_client_log5 API. */
+static svn_error_t *
+run_ra_get_log(apr_array_header_t *revision_ranges,
+               apr_array_header_t *paths,
+               apr_array_header_t *log_segments,
+               svn_client__pathrev_t *actual_loc,
+               svn_ra_session_t *ra_session,
+               /* The following are as per svn_client_log5. */ 
+               const apr_array_header_t *targets,
+               int limit,
+               svn_boolean_t discover_changed_paths,
+               svn_boolean_t strict_node_history,
+               svn_boolean_t include_merged_revisions,
+               const apr_array_header_t *revprops,
+               svn_log_entry_receiver_t real_receiver,
+               void *real_receiver_baton,
+               svn_client_ctx_t *ctx,
+               apr_pool_t *scratch_pool)
+{
+  int i;
+  pre_15_receiver_baton_t rb = {0};
+  apr_pool_t *iterpool;
+  svn_boolean_t has_log_revprops;
 
-    SVN_ERR(svn_ra_has_capability(ra_session, &has_log_revprops,
-                                  SVN_RA_CAPABILITY_LOG_REVPROPS, pool));
+  SVN_ERR(svn_ra_has_capability(ra_session, &has_log_revprops,
+                                SVN_RA_CAPABILITY_LOG_REVPROPS,
+                                scratch_pool));
 
-    if (!has_log_revprops) {
+  if (!has_log_revprops)
+    {
       /* See above pre-1.5 notes. */
       rb.ctx = ctx;
 
       /* Create ra session on first use */
-      rb.ra_session_pool = pool;
+      rb.ra_session_pool = scratch_pool;
       rb.ra_session_url = actual_loc->url;
     }
-  }
 
   /* It's a bit complex to correctly handle the special revision words
    * such as "BASE", "COMMITTED", and "PREV".  For example, if the
@@ -559,35 +682,54 @@ svn_client_log5(const apr_array_header_t
    * epg wonders if the repository could send a unified stream of log
    * entries if the paths and revisions were passed down.
    */
-  iterpool = svn_pool_create(pool);
+  iterpool = svn_pool_create(scratch_pool);
   for (i = 0; i < revision_ranges->nelts; i++)
     {
-      svn_revnum_t start_revnum, end_revnum, youngest_rev = SVN_INVALID_REVNUM;
+      const char *old_session_url;
       const char *path = APR_ARRAY_IDX(targets, 0, const char *);
       const char *local_abspath_or_url;
-      svn_opt_revision_range_t *range;
+      rev_range_t *range;
       limit_receiver_baton_t lb;
       svn_log_entry_receiver_t passed_receiver;
       void *passed_receiver_baton;
       const apr_array_header_t *passed_receiver_revprops;
+      svn_location_segment_t **matching_segment;
+      svn_revnum_t younger_rev;
 
       svn_pool_clear(iterpool);
 
       if (!svn_path_is_url(path))
-        SVN_ERR(svn_dirent_get_absolute(&local_abspath_or_url, path, iterpool));
+        SVN_ERR(svn_dirent_get_absolute(&local_abspath_or_url, path,
+                                        iterpool));
       else
         local_abspath_or_url = path;
 
-      range = APR_ARRAY_IDX(revision_ranges, i, svn_opt_revision_range_t *);
+      range = APR_ARRAY_IDX(revision_ranges, i, rev_range_t *);
 
-      SVN_ERR(svn_client__get_revision_number(&start_revnum, &youngest_rev,
-                                              ctx->wc_ctx, local_abspath_or_url,
-                                              ra_session, &range->start,
-                                              iterpool));
-      SVN_ERR(svn_client__get_revision_number(&end_revnum, &youngest_rev,
-                                              ctx->wc_ctx, local_abspath_or_url,
-                                              ra_session, &range->end,
-                                              iterpool));
+      /* Issue #4355: Account for renames spanning requested
+         revision ranges. */
+      younger_rev = MAX(range->range_start, range->range_end);
+      matching_segment = bsearch(&younger_rev, log_segments->elts,
+                                 log_segments->nelts, log_segments->elt_size,
+                                 compare_rev_to_segment);
+      SVN_ERR_ASSERT(*matching_segment);
+      
+      /* A segment with a NULL path means there is gap in the history.
+         We'll just proceed and let svn_ra_get_log2 fail with a useful
+         error...*/
+      if ((*matching_segment)->path != NULL)
+        {
+          /* ...but if there is history, then we must account for issue
+             #4355 and make sure our RA session is pointing at the correct
+             location. */
+          const char *segment_url = svn_path_url_add_component2(
+            actual_loc->repos_root_url, (*matching_segment)->path,
+            scratch_pool);
+          SVN_ERR(svn_client__ensure_ra_session_url(&old_session_url,
+                                                    ra_session,
+                                                    segment_url,
+                                                    scratch_pool));
+        }
 
       if (has_log_revprops)
         {
@@ -617,9 +759,9 @@ svn_client_log5(const apr_array_header_t
         }
 
       SVN_ERR(svn_ra_get_log2(ra_session,
-                              condensed_targets,
-                              start_revnum,
-                              end_revnum,
+                              paths,
+                              range->range_start,
+                              range->range_end,
                               limit,
                               discover_changed_paths,
                               strict_node_history,
@@ -642,3 +784,85 @@ svn_client_log5(const apr_array_header_t
 
   return SVN_NO_ERROR;
 }
+
+/*** Public Interface. ***/
+
+svn_error_t *
+svn_client_log5(const apr_array_header_t *targets,
+                const svn_opt_revision_t *peg_revision,
+                const apr_array_header_t *opt_rev_ranges,
+                int limit,
+                svn_boolean_t discover_changed_paths,
+                svn_boolean_t strict_node_history,
+                svn_boolean_t include_merged_revisions,
+                const apr_array_header_t *revprops,
+                svn_log_entry_receiver_t real_receiver,
+                void *real_receiver_baton,
+                svn_client_ctx_t *ctx,
+                apr_pool_t *pool)
+{
+  svn_ra_session_t *ra_session;
+  const char *old_session_url;
+  const char *ra_target;
+  svn_opt_revision_t youngest_opt_rev;
+  svn_revnum_t youngest_rev;
+  svn_revnum_t oldest_rev;
+  svn_opt_revision_t peg_rev;
+  svn_client__pathrev_t *actual_loc;
+  apr_array_header_t *log_segments;
+  apr_array_header_t *revision_ranges;
+  apr_array_header_t *relative_targets;
+
+  if (opt_rev_ranges->nelts == 0)
+    {
+      return svn_error_create
+        (SVN_ERR_CLIENT_BAD_REVISION, NULL,
+         _("Missing required revision specification"));
+    }
+
+  /* Make a copy of PEG_REVISION, we may need to change it to a
+     default value. */
+  peg_rev = *peg_revision;
+
+  SVN_ERR(resolve_log_targets(&relative_targets, &ra_target, &peg_rev,
+                              targets, ctx, pool, pool));
+
+  SVN_ERR(svn_client__ra_session_from_path2(&ra_session, &actual_loc,
+                                            ra_target, NULL, &peg_rev, &peg_rev,
+                                            ctx, pool));
+
+  /* Convert OPT_REV_RANGES to an array of rev_range_t and find the youngest
+     and oldest revision range that spans all of OPT_REV_RANGES. */
+  SVN_ERR(convert_opt_rev_array_to_rev_range_array(&revision_ranges,
+                                                   &youngest_rev,
+                                                   &oldest_rev,
+                                                   ra_session,
+                                                   ra_target,
+                                                   opt_rev_ranges, &peg_rev,
+                                                   ctx, pool,  pool));
+
+  /* Make ACTUAL_LOC and RA_SESSION point to the youngest operative rev. */
+  youngest_opt_rev.kind = svn_opt_revision_number;
+  youngest_opt_rev.value.number = youngest_rev;
+  SVN_ERR(svn_client__resolve_rev_and_url(&actual_loc, ra_session,
+                                          ra_target, &peg_rev,
+                                          &youngest_opt_rev, ctx, pool));
+  SVN_ERR(svn_client__ensure_ra_session_url(&old_session_url, ra_session,
+                                            actual_loc->url, pool));
+
+  /* Get the svn_location_segment_t's representing the requested log ranges. */
+  SVN_ERR(svn_client__repos_location_segments(&log_segments, ra_session,
+                                              actual_loc->url,
+                                              actual_loc->rev, /* peg */
+                                              actual_loc->rev, /* start */
+                                              oldest_rev,      /* end */
+                                              ctx, pool));
+
+  SVN_ERR(run_ra_get_log(revision_ranges, relative_targets, log_segments,
+                         actual_loc, ra_session, targets, limit,
+                         discover_changed_paths, strict_node_history,
+                         include_merged_revisions, revprops, real_receiver,
+                         real_receiver_baton, ctx, pool));
+
+  return SVN_NO_ERROR;
+}

Modified: subversion/branches/verify-keep-going/subversion/libsvn_client/merge.c
URL: http://svn.apache.org/viewvc/subversion/branches/verify-keep-going/subversion/libsvn_client/merge.c?rev=1489765&r1=1489764&r2=1489765&view=diff
==============================================================================
--- subversion/branches/verify-keep-going/subversion/libsvn_client/merge.c (original)
+++ subversion/branches/verify-keep-going/subversion/libsvn_client/merge.c Wed Jun  5 09:22:43 2013
@@ -436,7 +436,7 @@ check_repos_match(const merge_target_t *
   if (!svn_uri__is_ancestor(target->loc.repos_root_url, url))
     return svn_error_createf(
         SVN_ERR_UNSUPPORTED_FEATURE, NULL,
-         _("Url '%s' of '%s' is not in repository '%s'"),
+         _("URL '%s' of '%s' is not in repository '%s'"),
          url, svn_dirent_local_style(local_abspath, scratch_pool),
          target->loc.repos_root_url);
 
@@ -2403,7 +2403,7 @@ merge_file_deleted(const char *relpath,
                                    NULL, TRUE,
                                    scratch_pool));
     }
-  
+
   return SVN_NO_ERROR;
 }
 
@@ -2487,7 +2487,7 @@ merge_dir_opened(void **new_dir_baton,
             if (obstr_state == svn_wc_notify_state_obstructed)
               {
                 svn_boolean_t is_wcroot;
-        
+
                 SVN_ERR(svn_wc_check_root(&is_wcroot, NULL, NULL,
                                         merge_b->ctx->wc_ctx,
                                         local_abspath, scratch_pool));
@@ -2652,7 +2652,7 @@ merge_dir_opened(void **new_dir_baton,
               return SVN_NO_ERROR;
             }
         }
-      
+
       if (! (merge_b->dry_run
              && ((pdb && pdb->added) || db->add_is_replace)))
         {
@@ -2676,7 +2676,7 @@ merge_dir_opened(void **new_dir_baton,
             {
               svn_node_kind_t disk_kind;
 
-              SVN_ERR(svn_io_check_path(local_abspath, &disk_kind, 
+              SVN_ERR(svn_io_check_path(local_abspath, &disk_kind,
                                         scratch_pool));
 
               if (disk_kind == svn_node_dir)
@@ -2753,7 +2753,7 @@ merge_dir_opened(void **new_dir_baton,
               SVN_ERR(svn_wc_add4(merge_b->ctx->wc_ctx, local_abspath,
                                   svn_depth_infinity,
                                   original_url,
-                                  merge_b->merge_source.loc2->rev,
+                                  right_source->revision,
                                   merge_b->ctx->cancel_func,
                                   merge_b->ctx->cancel_baton,
                                   NULL, NULL /* no notify! */,
@@ -2953,7 +2953,7 @@ merge_dir_added(const char *relpath,
 
       copyfrom_url = svn_path_url_add_component2(merge_b->merge_source.loc2->url,
                                                  child, scratch_pool);
-      copyfrom_rev = merge_b->merge_source.loc2->rev;
+      copyfrom_rev = right_source->revision;
 
       SVN_ERR(check_repos_match(merge_b->target, parent_abspath, copyfrom_url,
                                 scratch_pool));
@@ -6229,7 +6229,7 @@ get_wc_explicit_mergeinfo_catalog(apr_ha
               err = svn_error_createf(
                 SVN_ERR_CLIENT_INVALID_MERGEINFO_NO_MERGETRACKING, err,
                 _("Invalid mergeinfo detected on '%s', "
-                  "mergetracking not possible"),
+                  "merge tracking not possible"),
                 svn_dirent_local_style(wc_path, scratch_pool));
             }
           return svn_error_trace(err);
@@ -7378,7 +7378,7 @@ do_file_merge(svn_mergeinfo_catalog_t re
               err = svn_error_createf(
                 SVN_ERR_CLIENT_INVALID_MERGEINFO_NO_MERGETRACKING, err,
                 _("Invalid mergeinfo detected on merge target '%s', "
-                  "mergetracking not possible"),
+                  "merge tracking not possible"),
                 svn_dirent_local_style(target_abspath, scratch_pool));
             }
           return svn_error_trace(err);
@@ -9012,24 +9012,23 @@ remove_noop_subtree_ranges(const merge_s
 
       svn_pool_clear(iterpool);
 
-      /* Issue #4269: Keep track of the longest common ancestor of all the
-         subtrees which require merges.  This may be a child of
-         TARGET->ABSPATH, which will allow us to narrow the log request
-         below. */
+      /* CHILD->REMAINING_RANGES will be NULL if child is absent. */
       if (child->remaining_ranges && child->remaining_ranges->nelts)
         {
+          /* Issue #4269: Keep track of the longest common ancestor of all the
+             subtrees which require merges.  This may be a child of
+             TARGET->ABSPATH, which will allow us to narrow the log request
+             below. */
           if (longest_common_subtree_ancestor)
             longest_common_subtree_ancestor = svn_dirent_get_longest_ancestor(
               longest_common_subtree_ancestor, child->abspath, scratch_pool);
           else
             longest_common_subtree_ancestor = child->abspath;
-        }
 
-      /* CHILD->REMAINING_RANGES will be NULL if child is absent. */
-      if (child->remaining_ranges && child->remaining_ranges->nelts)
-        SVN_ERR(svn_rangelist_merge2(subtree_remaining_ranges,
-                                     child->remaining_ranges,
-                                     scratch_pool, iterpool));
+          SVN_ERR(svn_rangelist_merge2(subtree_remaining_ranges,
+                                       child->remaining_ranges,
+                                       scratch_pool, iterpool));
+        }
     }
   svn_pool_destroy(iterpool);
 
@@ -11444,10 +11443,6 @@ find_reintegrate_merge(merge_source_t **
             &yc_ancestor, source.loc2, source.loc1, target_ra_session,
             ctx, scratch_pool, scratch_pool));
 
-  /* The source side of a reintegrate merge is not 'ancestral', except in
-   * the degenerate case where source == YCA. */
-  source.ancestral = (loc1->rev == yc_ancestor->rev);
-
   if (! yc_ancestor)
     return svn_error_createf(SVN_ERR_CLIENT_NOT_READY_TO_MERGE, NULL,
                              _("'%s@%ld' must be ancestrally related to "
@@ -11455,6 +11450,10 @@ find_reintegrate_merge(merge_source_t **
                              source.loc1->url, source.loc1->rev,
                              source.loc2->url, source.loc2->rev);
 
+  /* The source side of a reintegrate merge is not 'ancestral', except in
+   * the degenerate case where source == YCA. */
+  source.ancestral = (loc1->rev == yc_ancestor->rev);
+
   if (source.loc1->rev > yc_ancestor->rev)
     {
       /* Have we actually merged anything to the source from the
@@ -11742,6 +11741,40 @@ merge_peg_locked(conflict_report_t **con
   return SVN_NO_ERROR;
 }
 
+/* Details of an automatic merge. */
+typedef struct automatic_merge_t
+{
+  svn_client__pathrev_t *yca, *base, *right, *target;
+  svn_boolean_t is_reintegrate_like;
+  svn_boolean_t allow_mixed_rev, allow_local_mods, allow_switched_subtrees;
+} automatic_merge_t;
+
+static svn_error_t *
+client_find_automatic_merge(automatic_merge_t **merge_p,
+                            const char *source_path_or_url,
+                            const svn_opt_revision_t *source_revision,
+                            const char *target_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);
+
+static svn_error_t *
+do_automatic_merge_locked(conflict_report_t **conflict_report,
+                          const automatic_merge_t *merge,
+                          const char *target_abspath,
+                          svn_depth_t depth,
+                          svn_boolean_t diff_ignore_ancestry,
+                          svn_boolean_t force_delete,
+                          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 *result_pool,
+                          apr_pool_t *scratch_pool);
+
 svn_error_t *
 svn_client_merge_peg5(const char *source_path_or_url,
                       const apr_array_header_t *ranges_to_merge,
@@ -11771,7 +11804,7 @@ svn_client_merge_peg5(const char *source
   /* Do an automatic merge if no revision ranges are specified. */
   if (ranges_to_merge == NULL)
     {
-      svn_client_automatic_merge_t *merge;
+      automatic_merge_t *merge;
 
       if (ignore_mergeinfo)
         return svn_error_create(SVN_ERR_INCORRECT_PARAMS, NULL,
@@ -11779,24 +11812,31 @@ svn_client_merge_peg5(const char *source
                                   "ignoring mergeinfo"));
 
       /* Find the details of the merge needed. */
-      SVN_ERR(svn_client_find_automatic_merge(&merge,
-                                              source_path_or_url,
-                                              source_peg_revision,
-                                              target_wcpath,
-                                              allow_mixed_rev,
-                                              TRUE /*allow_local_mods*/,
-                                              TRUE /*allow_switched_subtrees*/,
-                                              ctx, pool, pool));
-
-      SVN_ERR(svn_client_do_automatic_merge(merge, target_wcpath, depth,
-                                            diff_ignore_ancestry,
-                                            force_delete, record_only,
-                                            dry_run, merge_options,
-                                            ctx, pool));
-
-      /* We already dealt with returning any conflict error, inside the
-       * above calls. */
-      conflict_report = NULL;
+      SVN_ERR(client_find_automatic_merge(
+                                    &merge,
+                                    source_path_or_url, source_peg_revision,
+                                    target_abspath,
+                                    allow_mixed_rev,
+                                    TRUE /*allow_local_mods*/,
+                                    TRUE /*allow_switched_subtrees*/,
+                                    ctx, pool, pool));
+
+      if (!dry_run)
+        SVN_WC__CALL_WITH_WRITE_LOCK(
+          do_automatic_merge_locked(&conflict_report,
+                                    merge,
+                                    target_abspath, depth,
+                                    diff_ignore_ancestry,
+                                    force_delete, record_only, dry_run,
+                                    merge_options, ctx, pool, pool),
+          ctx->wc_ctx, lock_abspath, FALSE /* lock_anchor */, pool);
+      else
+        SVN_ERR(do_automatic_merge_locked(&conflict_report,
+                                    merge,
+                                    target_abspath, depth,
+                                    diff_ignore_ancestry,
+                                    force_delete, record_only, dry_run,
+                                    merge_options, ctx, pool, pool));
     }
   else if (!dry_run)
     SVN_WC__CALL_WITH_WRITE_LOCK(
@@ -11973,20 +12013,32 @@ operative_rev_receiver(void *baton,
   *operative_rev = log_entry->revision;
 
   /* We've found the youngest merged or oldest eligible revision, so
-     we're done. */
+     we're done...
+
+     ...but wait, shouldn't we care if LOG_ENTRY->NON_INHERITABLE is
+     true?  Because if it is, then LOG_ENTRY->REVISION is only
+     partially merged/elgibile!  And our only caller,
+     find_last_merged_location (via short_circuit_mergeinfo_log) is
+     interested in *fully* merged revisions.  That's all true, but if
+     find_last_merged_location() finds the youngest merged revision it
+     will also check for the oldest eligible revision.  So in the case
+     the youngest merged rev is non-inheritable, the *same* non-inheritable
+     rev will be found as the oldest eligible rev -- and
+     find_last_merged_location() handles that situation. */
   return svn_error_create(SVN_ERR_CEASE_INVOCATION, NULL, NULL);
 }
 
-/* Wrapper around svn_client_mergeinfo_log2. All arguments are as per
-   that API.  The discover_changed_paths, depth, and revprops args to
-   svn_client_mergeinfo_log2 are always TRUE, svn_depth_infinity_t,
+/* Wrapper around svn_client__mergeinfo_log. All arguments are as per
+   that private API.  The discover_changed_paths, depth, and revprops args to
+   svn_client__mergeinfo_log are always TRUE, svn_depth_infinity_t,
    and NULL respectively.
 
    If RECEIVER raises a SVN_ERR_CEASE_INVOCATION error, but still sets
    *REVISION to a valid revnum, then clear the error.  Otherwise return
    any error. */
 static svn_error_t*
-short_circuit_mergeinfo_log(svn_boolean_t finding_merged,
+short_circuit_mergeinfo_log(svn_mergeinfo_catalog_t *target_mergeinfo_cat,
+                            svn_boolean_t finding_merged,
                             const char *target_path_or_url,
                             const svn_opt_revision_t *target_peg_revision,
                             const char *source_path_or_url,
@@ -11996,18 +12048,21 @@ short_circuit_mergeinfo_log(svn_boolean_
                             svn_log_entry_receiver_t receiver,
                             svn_revnum_t *revision,
                             svn_client_ctx_t *ctx,
+                            apr_pool_t *result_pool,
                             apr_pool_t *scratch_pool)
 {
-  svn_error_t *err = svn_client_mergeinfo_log2(finding_merged,
+  svn_error_t *err = svn_client__mergeinfo_log(finding_merged,
                                                target_path_or_url,
                                                target_peg_revision,
+                                               target_mergeinfo_cat,
                                                source_path_or_url,
                                                source_peg_revision,
                                                source_start_revision,
                                                source_end_revision,
                                                receiver, revision,
                                                TRUE, svn_depth_infinity,
-                                               NULL, ctx, scratch_pool);
+                                               NULL, ctx, result_pool,
+                                               scratch_pool);
 
   if (err)
     {
@@ -12077,6 +12132,7 @@ find_last_merged_location(svn_client__pa
   svn_opt_revision_t source_peg_rev, source_start_rev, source_end_rev,
     target_opt_rev;
   svn_revnum_t youngest_merged_rev = SVN_INVALID_REVNUM;
+  svn_mergeinfo_catalog_t target_mergeinfo_cat = NULL;
 
   source_peg_rev.kind = svn_opt_revision_number;
   source_peg_rev.value.number = source_branch->tip->rev;
@@ -12089,14 +12145,15 @@ find_last_merged_location(svn_client__pa
 
   /* Find the youngest revision fully merged from SOURCE_BRANCH to TARGET,
      if such a revision exists. */
-  SVN_ERR(short_circuit_mergeinfo_log(TRUE, /* Find merged */
+  SVN_ERR(short_circuit_mergeinfo_log(&target_mergeinfo_cat,
+                                      TRUE, /* Find merged */
                                       target->url, &target_opt_rev,
                                       source_branch->tip->url,
                                       &source_peg_rev,
                                       &source_end_rev, &source_start_rev,
                                       operative_rev_receiver,
                                       &youngest_merged_rev,
-                                      ctx, scratch_pool));
+                                      ctx, result_pool, scratch_pool));
 
   if (!SVN_IS_VALID_REVNUM(youngest_merged_rev))
     {
@@ -12107,20 +12164,31 @@ find_last_merged_location(svn_client__pa
   else
     {
       /* One or more revisions have already been completely merged from
-         SOURCE_BRANCH to TARGET, now find the oldest revision which is
-         still eligible to be merged, if such exists. */
+         SOURCE_BRANCH to TARGET, now find the oldest revision, older
+         than the youngest merged revision, which is still eligible to
+         be merged, if such exists. */
       branch_history_t *contiguous_source;
       svn_revnum_t base_rev;
       svn_revnum_t oldest_eligible_rev = SVN_INVALID_REVNUM;
 
-      SVN_ERR(short_circuit_mergeinfo_log(FALSE, /* Find eligible */
+      /* If the only revisions eligible are younger than the youngest merged
+         revision we can simply assume that the youngest eligible revision
+         is the youngest merged revision.  Obviously this may not be true!
+         The revisions between the youngest merged revision and the tip of
+         the branch may have several inoperative revisions -- they may *all*
+         be inoperative revisions!  But for the purpose of this function
+         (i.e. finding the youngest revision after the YCA where all revs have
+         been merged) that doesn't matter. */
+      source_end_rev.value.number = youngest_merged_rev;
+      SVN_ERR(short_circuit_mergeinfo_log(&target_mergeinfo_cat,
+                                          FALSE, /* Find eligible */
                                           target->url, &target_opt_rev,
                                           source_branch->tip->url,
                                           &source_peg_rev,
                                           &source_start_rev, &source_end_rev,
                                           operative_rev_receiver,
                                           &oldest_eligible_rev,
-                                          ctx, scratch_pool));
+                                          ctx, scratch_pool, scratch_pool));
 
       /* If there are revisions eligible for merging, use the oldest one
          to calculate the base.  Otherwise there are no operative revisions
@@ -12212,7 +12280,7 @@ find_base_on_target(svn_client__pathrev_
   return SVN_NO_ERROR;
 }
 
-/* The body of svn_client_find_automatic_merge(), which see.
+/* The body of client_find_automatic_merge(), which see.
  */
 static svn_error_t *
 find_automatic_merge(svn_client__pathrev_t **base_p,
@@ -12266,28 +12334,26 @@ find_automatic_merge(svn_client__pathrev
   return SVN_NO_ERROR;
 }
 
-/* Details of an automatic merge. */
-struct svn_client_automatic_merge_t
-{
-  svn_client__pathrev_t *yca, *base, *right, *target;
-  svn_boolean_t is_reintegrate_like;
-  svn_boolean_t allow_mixed_rev, allow_local_mods, allow_switched_subtrees;
-};
-
-svn_error_t *
-svn_client_find_automatic_merge_no_wc(
-                                 svn_client_automatic_merge_t **merge_p,
-                                 const char *source_path_or_url,
-                                 const svn_opt_revision_t *source_revision,
-                                 const char *target_path_or_url,
-                                 const svn_opt_revision_t *target_revision,
-                                 svn_client_ctx_t *ctx,
-                                 apr_pool_t *result_pool,
-                                 apr_pool_t *scratch_pool)
+/** Find out what kind of automatic merge would be needed, when the target
+ * is only known as a repository location rather than a WC.
+ *
+ * Like find_automatic_merge() except that the target is
+ * specified by @a target_path_or_url at @a target_revision, which must
+ * refer to a repository location, instead of by a WC path argument.
+ */
+static svn_error_t *
+find_automatic_merge_no_wc(automatic_merge_t **merge_p,
+                           const char *source_path_or_url,
+                           const svn_opt_revision_t *source_revision,
+                           const char *target_path_or_url,
+                           const svn_opt_revision_t *target_revision,
+                           svn_client_ctx_t *ctx,
+                           apr_pool_t *result_pool,
+                           apr_pool_t *scratch_pool)
 {
   source_and_target_t *s_t = apr_palloc(scratch_pool, sizeof(*s_t));
   svn_client__pathrev_t *target_loc;
-  svn_client_automatic_merge_t *merge = apr_palloc(result_pool, sizeof(*merge));
+  automatic_merge_t *merge = apr_palloc(result_pool, sizeof(*merge));
 
   /* Source */
   SVN_ERR(svn_client__ra_session_from_path2(
@@ -12315,32 +12381,49 @@ svn_client_find_automatic_merge_no_wc(
   return SVN_NO_ERROR;
 }
 
-svn_error_t *
-svn_client_find_automatic_merge(svn_client_automatic_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_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)
+/* Find the information needed to merge all unmerged changes from a source
+ * branch into a target branch.
+ *
+ * Set @a *merge_p to the information needed to merge all unmerged changes
+ * (up to @a source_revision) from the source branch @a source_path_or_url
+ * at @a source_revision into the target WC at @a target_abspath.
+ *
+ * The flags @a allow_mixed_rev, @a allow_local_mods and
+ * @a allow_switched_subtrees enable merging into a WC that is in any or all
+ * of the states described by their names, but only if this function decides
+ * that the merge will be in the same direction as the last automatic merge.
+ * If, on the other hand, the last automatic merge was in the opposite
+ * direction, then such states of the WC are not allowed regardless
+ * of these flags.  This function merely records these flags in the
+ * @a *merge_p structure; do_automatic_merge_locked() checks the WC
+ * state for compliance.
+ *
+ * Allocate the @a *merge_p structure in @a result_pool.
+ */
+static svn_error_t *
+client_find_automatic_merge(automatic_merge_t **merge_p,
+                            const char *source_path_or_url,
+                            const svn_opt_revision_t *source_revision,
+                            const char *target_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)
 {
-  const char *target_abspath;
   source_and_target_t *s_t = apr_palloc(result_pool, sizeof(*s_t));
-  svn_client_automatic_merge_t *merge = apr_palloc(result_pool, sizeof(*merge));
-
-  SVN_ERR(svn_dirent_get_absolute(&target_abspath, target_wcpath, scratch_pool));
+  automatic_merge_t *merge = apr_palloc(result_pool, sizeof(*merge));
 
-  /* "Open" the target WC.  We're not going to check the target WC for
-   * mixed-rev, local mods or switched subtrees yet.  After we find out
-   * what kind of merge is required, then if a reintegrate-like merge is
-   * required we'll do the stricter checks, in do_automatic_merge_locked(). */
+  /* "Open" the target WC.  Check the target WC for mixed-rev, local mods and
+   * switched subtrees yet to faster exit and notify user before contacting
+   * with server.  After we find out what kind of merge is required, then if a
+   * reintegrate-like merge is required we'll do the stricter checks, in
+   * do_automatic_merge_locked(). */
   SVN_ERR(open_target_wc(&s_t->target, target_abspath,
-                         TRUE /*allow_mixed_rev*/,
-                         TRUE /*allow_local_mods*/,
-                         TRUE /*allow_switched_subtrees*/,
+                         allow_mixed_rev,
+                         allow_local_mods,
+                         allow_switched_subtrees,
                          ctx, result_pool, scratch_pool));
 
   /* Open RA sessions to the source and target trees. */
@@ -12356,7 +12439,7 @@ svn_client_find_automatic_merge(svn_clie
 
   /* Check source is in same repos as target. */
   SVN_ERR(check_same_repos(s_t->source, source_path_or_url,
-                           &s_t->target->loc, target_wcpath,
+                           &s_t->target->loc, target_abspath,
                            TRUE /* strict_urls */, scratch_pool));
 
   SVN_ERR(find_automatic_merge(&merge->base, &merge->is_reintegrate_like, s_t,
@@ -12374,7 +12457,8 @@ svn_client_find_automatic_merge(svn_clie
   return SVN_NO_ERROR;
 }
 
-/* The body of svn_client_do_automatic_merge(), which see.
+/* Perform an automatic merge, given the information in MERGE which
+ * must have come from calling client_find_automatic_merge().
  *
  * Four locations are inputs: YCA, BASE, RIGHT, TARGET, as shown
  * depending on whether the base is on the source branch or the target
@@ -12403,7 +12487,7 @@ svn_client_find_automatic_merge(svn_clie
  */
 static svn_error_t *
 do_automatic_merge_locked(conflict_report_t **conflict_report,
-                          const svn_client_automatic_merge_t *merge,
+                          const automatic_merge_t *merge,
                           const char *target_abspath,
                           svn_depth_t depth,
                           svn_boolean_t diff_ignore_ancestry,
@@ -12531,65 +12615,42 @@ do_automatic_merge_locked(conflict_repor
 }
 
 svn_error_t *
-svn_client_do_automatic_merge(const svn_client_automatic_merge_t *merge,
-                              const char *target_wcpath,
-                              svn_depth_t depth,
-                              svn_boolean_t diff_ignore_ancestry,
-                              svn_boolean_t force_delete,
-                              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)
+svn_client_get_merging_summary(svn_boolean_t *needs_reintegration,
+                               const char **yca_url, svn_revnum_t *yca_rev,
+                               const char **base_url, svn_revnum_t *base_rev,
+                               const char **right_url, svn_revnum_t *right_rev,
+                               const char **target_url, svn_revnum_t *target_rev,
+                               const char **repos_root_url,
+                               const char *source_path_or_url,
+                               const svn_opt_revision_t *source_revision,
+                               const char *target_path_or_url,
+                               const svn_opt_revision_t *target_revision,
+                               svn_client_ctx_t *ctx,
+                               apr_pool_t *result_pool,
+                               apr_pool_t *scratch_pool)
 {
-  const char *target_abspath, *lock_abspath;
-  conflict_report_t *conflict_report;
-
-  SVN_ERR(get_target_and_lock_abspath(&target_abspath, &lock_abspath,
-                                      target_wcpath, ctx, pool));
+  svn_boolean_t target_is_wc;
+  automatic_merge_t *merge;
 
-  if (!dry_run)
-    SVN_WC__CALL_WITH_WRITE_LOCK(
-      do_automatic_merge_locked(&conflict_report,
-                                merge,
-                                target_abspath, depth,
-                                diff_ignore_ancestry,
-                                force_delete, record_only, dry_run,
-                                merge_options, ctx, pool, pool),
-      ctx->wc_ctx, lock_abspath, FALSE /* lock_anchor */, pool);
+  target_is_wc = (! svn_path_is_url(target_path_or_url))
+                 && (target_revision->kind == svn_opt_revision_unspecified
+                     || target_revision->kind == svn_opt_revision_working);
+  if (target_is_wc)
+    SVN_ERR(client_find_automatic_merge(
+              &merge,
+              source_path_or_url, source_revision,
+              target_path_or_url,
+              TRUE, TRUE, TRUE,  /* allow_* */
+              ctx, scratch_pool, scratch_pool));
   else
-    SVN_ERR(do_automatic_merge_locked(&conflict_report,
-                                merge,
-                                target_abspath, depth,
-                                diff_ignore_ancestry,
-                                force_delete, record_only, dry_run,
-                                merge_options, ctx, pool, pool));
-
-  SVN_ERR(make_merge_conflict_error(conflict_report, pool));
-  return SVN_NO_ERROR;
-}
+    SVN_ERR(find_automatic_merge_no_wc(
+              &merge,
+              source_path_or_url, source_revision,
+              target_path_or_url, target_revision,
+              ctx, scratch_pool, scratch_pool));
 
-svn_boolean_t
-svn_client_automatic_merge_is_reintegrate_like(
-        const svn_client_automatic_merge_t *merge)
-{
-  return merge->is_reintegrate_like;
-}
-
-svn_error_t *
-svn_client_automatic_merge_get_locations(
-                                const char **yca_url,
-                                svn_revnum_t *yca_rev,
-                                const char **base_url,
-                                svn_revnum_t *base_rev,
-                                const char **right_url,
-                                svn_revnum_t *right_rev,
-                                const char **target_url,
-                                svn_revnum_t *target_rev,
-                                const char **repos_root_url,
-                                const svn_client_automatic_merge_t *merge,
-                                apr_pool_t *result_pool)
-{
+  if (needs_reintegration)
+    *needs_reintegration = merge->is_reintegrate_like;
   if (yca_url)
     *yca_url = apr_pstrdup(result_pool, merge->yca->url);
   if (yca_rev)