Merge from trunk r30215, r30221, r30225, r30226,and r30230.

git-svn-id: https://svn.apache.org/repos/asf/subversion/branches/1.5.x-r30215@870310 13f79535-47bb-0310-9956-ffa450edef68
diff --git a/subversion/include/svn_client.h b/subversion/include/svn_client.h
index 2831f21..a9d7f98 100644
--- a/subversion/include/svn_client.h
+++ b/subversion/include/svn_client.h
@@ -2643,8 +2643,7 @@
  *
  * Use @a pool for all necessary allocations.
  *
- * If the server doesn't support retrieval of mergeinfo (which will
- * never happen for file:// URLs), return an @c
+ * If the server doesn't support retrieval of mergeinfo, return an @c
  * SVN_ERR_UNSUPPORTED_FEATURE error.
  *
  * @note Unlike most APIs which deal with mergeinfo, this one returns
@@ -2662,6 +2661,30 @@
 
 
 /**
+ * Drive log entry callbacks @c receiver / @c receiver_baton with the
+ * revisions merged from @a merge_source_url (as of @a
+ * src_peg_revision) into @a path_or_url (as of @a peg_revision).  @a
+ * ctx is a context used for authentication.  @c
+ * discover_changed_paths is the same as for svn_client_log4().  Use
+ * @a pool for all necessary allocations.
+ *
+ * If the server doesn't support retrieval of mergeinfo, return an @c
+ * SVN_ERR_UNSUPPORTED_FEATURE error.
+ *
+ * @since New in 1.6.
+ */
+svn_error_t *
+svn_client_mergeinfo_log_merged(const char *path_or_url,
+                                const svn_opt_revision_t *peg_revision,
+                                const char *merge_source_url,
+                                const svn_opt_revision_t *src_peg_revision,
+                                svn_log_entry_receiver_t receiver,
+                                void *receiver_baton,
+                                svn_boolean_t discover_changed_paths,
+                                svn_client_ctx_t *ctx,
+                                apr_pool_t *pool);
+
+/**
  * Set @a *rangelist to a list of <tt>svn_merge_range_t *</tt>
  * items representing ranges of revisions which have not yet been
  * merged from @a merge_source_url into @a path_or_url as of @a
@@ -2670,8 +2693,7 @@
  *
  * Use @a pool for all necessary allocations.
  *
- * If the server doesn't support retrieval of mergeinfo (which will
- * never happen for file:// URLs), return an @c
+ * If the server doesn't support retrieval of mergeinfo, return an @c
  * SVN_ERR_UNSUPPORTED_FEATURE error.
  *
  * @since New in 1.5.
@@ -2684,7 +2706,29 @@
                                    svn_client_ctx_t *ctx,
                                    apr_pool_t *pool);
 
-
+/**
+ * Drive log entry callbacks @c receiver / @c receiver_baton with the
+ * revisions eligible for merge from @a merge_source_url (as of @a
+ * src_peg_revision) into @a path_or_url (as of @a peg_revision).  @a
+ * ctx is a context used for authentication.  @c
+ * discover_changed_paths is the same as for svn_client_log4().  Use
+ * @a pool for all necessary allocations.
+ *
+ * If the server doesn't support retrieval of mergeinfo, return an @c
+ * SVN_ERR_UNSUPPORTED_FEATURE error.
+ *
+ * @since New in 1.6.
+ */
+svn_error_t *
+svn_client_mergeinfo_log_eligible(const char *path_or_url,
+                                  const svn_opt_revision_t *peg_revision,
+                                  const char *merge_source_url,
+                                  const svn_opt_revision_t *src_peg_revision,
+                                  svn_log_entry_receiver_t receiver,
+                                  void *receiver_baton,
+                                  svn_boolean_t discover_changed_paths,
+                                  svn_client_ctx_t *ctx,
+                                  apr_pool_t *pool);
 
 /** @} */
 
diff --git a/subversion/libsvn_client/mergeinfo.c b/subversion/libsvn_client/mergeinfo.c
index 93495f7..95f6cd4 100644
--- a/subversion/libsvn_client/mergeinfo.c
+++ b/subversion/libsvn_client/mergeinfo.c
@@ -1030,10 +1030,197 @@
 }
 
 
+struct filter_log_entry_baton_t
+{
+  apr_array_header_t *rangelist;
+  svn_log_entry_receiver_t log_receiver;
+  void *log_receiver_baton;
+  svn_client_ctx_t *ctx;
+};
+
+/* Implements the svn_log_entry_receiver_t interface.  BATON is a
+   `struct filter_log_entry_baton_t *' */
+static svn_error_t *
+filter_log_entry_with_rangelist(void *baton,
+                                svn_log_entry_t *log_entry,
+                                apr_pool_t *pool)
+{
+  struct filter_log_entry_baton_t *fleb = baton;
+  svn_merge_range_t *range;
+  apr_array_header_t *intersection, *this_rangelist;
+
+  if (fleb->ctx->cancel_func)
+    SVN_ERR(fleb->ctx->cancel_func(fleb->ctx->cancel_baton));
+
+  this_rangelist = apr_array_make(pool, 1, sizeof(svn_merge_range_t *));
+  range = apr_pcalloc(pool, sizeof(*range));
+  range->start = log_entry->revision - 1;
+  range->end = log_entry->revision;
+  range->inheritable = TRUE;
+  APR_ARRAY_PUSH(this_rangelist, svn_merge_range_t *) = range;
+  SVN_ERR(svn_rangelist_intersect(&intersection, fleb->rangelist, 
+                                  this_rangelist, pool));
+  if (! (intersection && intersection->nelts))
+    return SVN_NO_ERROR;
+
+  assert (intersection->nelts == 1);
+  return fleb->log_receiver(fleb->log_receiver_baton, log_entry, pool);
+}
+
+
+/* Implements the svn_log_entry_receiver_t interface.  BATON is a
+   pointer to a mergeinfo rangelist array. */
+static svn_error_t *
+append_log_rev_to_rangelist(void *baton,
+                            svn_log_entry_t *log_entry,
+                            apr_pool_t *pool)
+{
+  apr_array_header_t *rangelist = baton;
+  svn_merge_range_t *range = apr_pcalloc(rangelist->pool, sizeof(*range));
+  APR_ARRAY_PUSH(rangelist, svn_merge_range_t *) = range;
+  range->start = log_entry->revision - 1;
+  range->end = log_entry->revision;
+  range->inheritable = TRUE;
+  return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+logs_for_mergeinfo_rangelist(const char *source_url,
+                             apr_array_header_t *rangelist,
+                             svn_boolean_t discover_changed_paths,
+                             svn_log_entry_receiver_t log_receiver,
+                             void *log_receiver_baton,
+                             svn_client_ctx_t *ctx,
+                             apr_pool_t *pool)
+{
+  apr_array_header_t *target;
+  svn_merge_range_t *oldest_range, *youngest_range;
+  svn_opt_revision_t oldest_rev, youngest_rev;
+  struct filter_log_entry_baton_t fleb;
+
+  if (! rangelist->nelts)
+    return SVN_NO_ERROR;
+
+  /* Sort the rangelist. */
+  qsort(rangelist->elts, rangelist->nelts, 
+        rangelist->elt_size, svn_sort_compare_ranges);
+
+  /* Build a single-member log target list using SOURCE_URL. */
+  target = apr_array_make(pool, 1, sizeof(const char *));
+  APR_ARRAY_PUSH(target, const char *) = source_url;
+
+  /* Calculate and construct the bounds of our log request. */
+  youngest_range = APR_ARRAY_IDX(rangelist, rangelist->nelts - 1, 
+                                 svn_merge_range_t *);
+  youngest_rev.kind = svn_opt_revision_number;
+  youngest_rev.value.number = youngest_range->end;
+  oldest_range = APR_ARRAY_IDX(rangelist, 0, svn_merge_range_t *);
+  oldest_rev.kind = svn_opt_revision_number;
+  oldest_rev.value.number = oldest_range->start;
+
+  /* Build the log filtering callback baton. */
+  fleb.rangelist = rangelist;
+  fleb.log_receiver = log_receiver;
+  fleb.log_receiver_baton = log_receiver_baton;
+  fleb.ctx = ctx;
+
+  /* Drive the log. */
+  SVN_ERR(svn_client_log4(target, &youngest_rev, &oldest_rev, &youngest_rev, 
+                          0, discover_changed_paths, FALSE, FALSE, NULL, 
+                          filter_log_entry_with_rangelist, &fleb, ctx, pool));
+
+  /* Check for cancellation. */
+  if (ctx->cancel_func)
+    SVN_ERR(ctx->cancel_func(ctx->cancel_baton));
+
+  return SVN_NO_ERROR;
+}
+
 
 /*** Public APIs ***/
 
 svn_error_t *
+svn_client_mergeinfo_log_merged(const char *path_or_url,
+                                const svn_opt_revision_t *peg_revision,
+                                const char *merge_source_url,
+                                const svn_opt_revision_t *src_peg_revision,
+                                svn_log_entry_receiver_t log_receiver,
+                                void *log_receiver_baton,
+                                svn_boolean_t discover_changed_paths,
+                                svn_client_ctx_t *ctx,
+                                apr_pool_t *pool)
+{
+  const char *repos_root, *log_target = NULL;
+  svn_mergeinfo_t tgt_mergeinfo, source_history, mergeinfo;
+  apr_array_header_t *rangelist;
+  apr_hash_index_t *hi;
+  svn_revnum_t youngest_rev = SVN_INVALID_REVNUM;
+
+  assert(svn_path_is_url(merge_source_url));
+    
+  /* Step 1: We need the union of PATH_OR_URL@PEG_REVISION's mergeinfo
+     and MERGE_SOURCE_URL's history.  It's not enough to do path
+     matching, because renames in the history of MERGE_SOURCE_URL
+     throw that all in a tizzy.  Of course, if there's no mergeinfo on
+     the target, that vastly simplifies matters (we'll have nothing to
+     do). */
+  /* This get_mergeinfo() call doubles as a mergeinfo capabilities check. */
+  SVN_ERR(get_mergeinfo(&tgt_mergeinfo, &repos_root, path_or_url, 
+                        peg_revision, ctx, pool));
+  if (! tgt_mergeinfo)
+    return SVN_NO_ERROR;
+  SVN_ERR(svn_client__get_history_as_mergeinfo(&source_history, 
+                                               merge_source_url,
+                                               src_peg_revision,
+                                               SVN_INVALID_REVNUM,
+                                               SVN_INVALID_REVNUM,
+                                               NULL, NULL, ctx, pool));
+  SVN_ERR(svn_mergeinfo_intersect(&mergeinfo, tgt_mergeinfo, 
+                                  source_history, pool));
+
+  /* Step 2: Now, we iterate over the eligible paths/rangelists to
+     find the youngest revision (and its associated path).  Because
+     SOURCE_HISTORY had the property that a revision could appear in
+     at most one mergeinfo path, that same property is true of
+     MERGEINFO (which is a subset of SOURCE_HISTORY).  We'll use this
+     information to bound a run of the logs of the source's history so
+     we can filter out no-op merge revisions.  While here, we'll
+     collapse our rangelists into a single one.  */
+  rangelist = apr_array_make(pool, 64, sizeof(svn_merge_range_t *));
+  for (hi = apr_hash_first(pool, mergeinfo); hi; hi = apr_hash_next(hi))
+    {
+      const void *key;
+      void *val;
+      svn_merge_range_t *range;
+      apr_array_header_t *list;
+
+      apr_hash_this(hi, &key, NULL, &val);
+      list = val;
+      range = APR_ARRAY_IDX(list, list->nelts - 1, svn_merge_range_t *);
+      if ((! SVN_IS_VALID_REVNUM(youngest_rev)) 
+          || (range->end > youngest_rev))
+        {
+          youngest_rev = range->end;
+          log_target = key;
+        }
+      SVN_ERR(svn_rangelist_merge(&rangelist, list, pool));
+    }
+
+  /* Nothing eligible?  Get outta here. */
+  if (! rangelist->nelts)
+    return SVN_NO_ERROR;
+
+  /* Step 3: Finally, we run 'svn log' to drive our log receiver, but
+     using a receiver filter to only allow revisions to pass through
+     that are in our rangelist. */
+  log_target = svn_path_url_add_component(repos_root, log_target + 1, pool);
+  return logs_for_mergeinfo_rangelist(log_target, rangelist, 
+                                      discover_changed_paths, log_receiver, 
+                                      log_receiver_baton, ctx, pool);
+}
+
+
+svn_error_t *
 svn_client_mergeinfo_get_merged(apr_hash_t **mergeinfo_p,
                                 const char *path_or_url,
                                 const svn_opt_revision_t *peg_revision,
@@ -1075,27 +1262,31 @@
 
 
 svn_error_t *
-svn_client_mergeinfo_get_available(apr_array_header_t **rangelist,
-                                   const char *path_or_url,
-                                   const svn_opt_revision_t *peg_revision,
-                                   const char *merge_source_url,
-                                   svn_client_ctx_t *ctx,
-                                   apr_pool_t *pool)
+svn_client_mergeinfo_log_eligible(const char *path_or_url,
+                                  const svn_opt_revision_t *peg_revision,
+                                  const char *merge_source_url,
+                                  const svn_opt_revision_t *src_peg_revision,
+                                  svn_log_entry_receiver_t log_receiver,
+                                  void *log_receiver_baton,
+                                  svn_boolean_t discover_changed_paths,
+                                  svn_client_ctx_t *ctx,
+                                  apr_pool_t *pool)
 {
   svn_mergeinfo_t mergeinfo, history, source_history, available;
   apr_hash_index_t *hi;
   svn_ra_session_t *ra_session;
-  int num_ranges = 0;
   const char *repos_root;
   apr_pool_t *sesspool;
-  svn_opt_revision_t head_revision;
-  head_revision.kind = svn_opt_revision_head;
+  svn_revnum_t youngest_rev = SVN_INVALID_REVNUM;
+  apr_array_header_t *rangelist;
+  const char *log_target = NULL;
 
   assert(svn_path_is_url(merge_source_url));
   
   /* Step 1: Across the set of possible merges, see what's already
      been merged into PATH_OR_URL@PEG_REVISION (or what's already part
      of the history it shares with that of MERGE_SOURCE_URL.  */
+  /* This get_mergeinfo() call doubles as a mergeinfo capabilities check. */
   SVN_ERR(get_mergeinfo(&mergeinfo, &repos_root, path_or_url, 
                         peg_revision, ctx, pool));
   SVN_ERR(svn_client__get_history_as_mergeinfo(&history, 
@@ -1117,7 +1308,7 @@
                                                TRUE, ctx, sesspool));
   SVN_ERR(svn_client__get_history_as_mergeinfo(&source_history, 
                                                merge_source_url,
-                                               &head_revision, 
+                                               src_peg_revision,
                                                SVN_INVALID_REVNUM,
                                                SVN_INVALID_REVNUM,
                                                ra_session, NULL, ctx, pool));
@@ -1127,21 +1318,71 @@
      (SOURCE_HISTORY) the merges already present in our PATH_OR_URL. */
   SVN_ERR(svn_mergeinfo_remove(&available, mergeinfo, source_history, pool));
 
-  /* Finally, we want to provide a simple, single revision range list
-     to our caller.  Now, interestingly, if MERGE_SOURCE_URL has been
-     renamed over time, there's good chance that set of available
-     merges have different paths assigned to them.  Fortunately, we
-     know that we can't have any two paths in AVAILABLE with
-     overlapping revisions (because the original SOURCE_HISTORY also
-     had this property).  So we'll just collapse into one rangelist
-     all the rangelists across all the paths in AVAILABLE. */
-  *rangelist = apr_array_make(pool, num_ranges, sizeof(svn_merge_range_t *));
+  /* Step 3: Now, we iterate over the eligible paths/rangelists to
+     find the youngest revision (and its associated path).  Because
+     SOURCE_HISTORY had the property that a revision could appear in
+     at most one mergeinfo path, that same property is true of
+     AVAILABLE (which is a subset of SOURCE_HISTORY).  We'll use this
+     information to bound a run of the logs of the source's history so
+     we can filter out no-op merge revisions.  While here, we'll
+     collapse our rangelists into a single one.  */
+  rangelist = apr_array_make(pool, 64, sizeof(svn_merge_range_t *));
   for (hi = apr_hash_first(pool, available); hi; hi = apr_hash_next(hi))
     {
+      const void *key;
       void *val;
-      apr_hash_this(hi, NULL, NULL, &val);
-      SVN_ERR(svn_rangelist_merge(rangelist, val, pool));
+      svn_merge_range_t *range;
+      apr_array_header_t *list;
+
+      apr_hash_this(hi, &key, NULL, &val);
+      list = val;
+      range = APR_ARRAY_IDX(list, list->nelts - 1, svn_merge_range_t *);
+      if ((! SVN_IS_VALID_REVNUM(youngest_rev)) 
+          || (range->end > youngest_rev))
+        {
+          youngest_rev = range->end;
+          log_target = key;
+        }
+      SVN_ERR(svn_rangelist_merge(&rangelist, list, pool));
     }
+
+  /* Nothing eligible?  Get outta here. */
+  if (! rangelist->nelts)
+    return SVN_NO_ERROR;
+
+  /* Step 4: Finally, we run 'svn log' to drive our log receiver, but
+     using a receiver filter to only allow revisions to pass through
+     that are in our rangelist. */
+  log_target = svn_path_url_add_component(repos_root, log_target + 1, pool);
+  return logs_for_mergeinfo_rangelist(log_target, rangelist, 
+                                      discover_changed_paths, log_receiver, 
+                                      log_receiver_baton, ctx, pool);
+}
+
+
+svn_error_t *
+svn_client_mergeinfo_get_available(apr_array_header_t **rangelist,
+                                   const char *path_or_url,
+                                   const svn_opt_revision_t *peg_revision,
+                                   const char *merge_source_url,
+                                   svn_client_ctx_t *ctx,
+                                   apr_pool_t *pool)
+{
+  svn_opt_revision_t head_revision;
+  apr_array_header_t *available = 
+    apr_array_make(pool, 64, sizeof(svn_merge_range_t *));
+  head_revision.kind = svn_opt_revision_head;
+  SVN_ERR(svn_client_mergeinfo_log_eligible(path_or_url, peg_revision,
+                                            merge_source_url, &head_revision,
+                                            append_log_rev_to_rangelist,
+                                            available, FALSE, ctx, pool));
+  qsort(available->elts, available->nelts, 
+        available->elt_size, svn_sort_compare_ranges);
+  SVN_ERR(svn_rangelist_merge(&available,
+                              apr_array_make(pool, 0, 
+                                             sizeof(svn_merge_range_t *)),
+                              pool));
+  *rangelist = available;
   return SVN_NO_ERROR;
 }
 
@@ -1157,7 +1398,7 @@
   const char *copyfrom_path;
   apr_array_header_t *list;
   svn_revnum_t copyfrom_rev;
-  apr_hash_t *mergeinfo;
+  svn_mergeinfo_t mergeinfo;
   apr_hash_index_t *hi;
 
   list = apr_array_make(pool, 1, sizeof(const char *));
@@ -1179,8 +1420,8 @@
   */
 
   /* ### TODO: Share ra_session batons to improve efficiency? */
-  SVN_ERR(svn_client__get_repos_root(&repos_root, path_or_url, peg_revision,
-                                     NULL, ctx, pool));
+  SVN_ERR(get_mergeinfo(&mergeinfo, &repos_root, path_or_url, 
+                        peg_revision, ctx, pool));
   SVN_ERR(svn_client__get_copy_source(path_or_url, peg_revision,
                                       &copyfrom_path, &copyfrom_rev,
                                       ctx, pool));
@@ -1193,16 +1434,18 @@
       APR_ARRAY_PUSH(list, const char *) = copyfrom_path;
     }
 
-  SVN_ERR(svn_client_mergeinfo_get_merged(&mergeinfo, path_or_url,
-                                          peg_revision, ctx, pool));
   if (mergeinfo)
     {
       for (hi = apr_hash_first(NULL, mergeinfo); hi; hi = apr_hash_next(hi))
         {
-          const char *merge_path;
-          apr_hash_this(hi, (void *)(&merge_path), NULL, NULL);
-          if (copyfrom_path == NULL || strcmp(merge_path, copyfrom_path) != 0)
-            APR_ARRAY_PUSH(list, const char *) = merge_path;
+          const void *key;
+          const char *rel_path;
+
+          apr_hash_this(hi, &key, NULL, NULL);
+          rel_path = key;
+          if (copyfrom_path == NULL || strcmp(rel_path, copyfrom_path) != 0)
+            APR_ARRAY_PUSH(list, const char *) = \
+              svn_path_url_add_component(repos_root, rel_path + 1, pool);
         }
     }
 
diff --git a/subversion/libsvn_client/util.c b/subversion/libsvn_client/util.c
index f8a02ad..fb03c25 100644
--- a/subversion/libsvn_client/util.c
+++ b/subversion/libsvn_client/util.c
@@ -158,7 +158,7 @@
      the entry.  The entry might not hold a URL -- in that case, we'll
      need a fallback plan. */
   if (*repos_root == NULL)
-    *repos_root = entry->repos;
+    *repos_root = apr_pstrdup(pool, entry->repos);
 
   return SVN_NO_ERROR;
 }
diff --git a/subversion/svn/cl.h b/subversion/svn/cl.h
index 04b6d3f..1e8d924 100644
--- a/subversion/svn/cl.h
+++ b/subversion/svn/cl.h
@@ -100,6 +100,23 @@
 svn_cl__accept_t
 svn_cl__accept_from_word(const char *word);
 
+
+/*** Mergeinfo flavors. ***/
+
+/* --show-revs values */
+typedef enum {
+  svn_cl__show_revs_invalid = -1,
+  svn_cl__show_revs_merged,
+  svn_cl__show_revs_eligible
+} svn_cl__show_revs_t;
+
+/* --show-revs user input words */
+#define SVN_CL__SHOW_REVS_MERGED   "merged"
+#define SVN_CL__SHOW_REVS_ELIGIBLE "eligible"
+
+/* Return svn_cl__show_revs_t value corresponding to word. */
+svn_cl__show_revs_t
+svn_cl__show_revs_from_word(const char *word);
 
 
 /*** Command dispatch. ***/
@@ -185,7 +202,7 @@
   svn_boolean_t parents;         /* create intermediate directories */
   svn_boolean_t use_merge_history; /* use/display extra merge information */
   svn_cl__accept_t accept_which; /* how to handle conflicts */
-  const char *from_source;       /* merge source to query (svn mergeinfo) */
+  svn_cl__show_revs_t show_revs; /* mergeinfo flavor */
   svn_depth_t set_depth;         /* new sticky ambient depth value */
   svn_boolean_t reintegrate;     /* use "reintegrate" merge-source heuristic */
 } svn_cl__opt_state_t;
diff --git a/subversion/svn/main.c b/subversion/svn/main.c
index a318f9c..813b364 100644
--- a/subversion/svn/main.c
+++ b/subversion/svn/main.c
@@ -100,7 +100,7 @@
   opt_with_all_revprops,
   opt_parents,
   opt_accept,
-  opt_from_source,
+  opt_show_revs,
   opt_reintegrate
 } svn_cl__longopt_t;
 
@@ -270,8 +270,11 @@
                        "\n                            "
                        " '" SVN_CL__ACCEPT_EDIT "',"
                        " '" SVN_CL__ACCEPT_LAUNCH "')")},
-  {"from-source",   opt_from_source, 1,
-                    N_("query a particular merge source URL")},
+  {"show-revs",     opt_show_revs, 1,
+                    N_("specify which collection of revisions to display\n"
+                       "                             "
+                       "('" SVN_CL__SHOW_REVS_MERGED "',"
+                       " '" SVN_CL__SHOW_REVS_ELIGIBLE "')")},
   {"reintegrate",   opt_reintegrate, 0,
                     N_("lump-merge all of source URL's unmerged changes")},
 
@@ -622,8 +625,14 @@
 
   { "mergeinfo", svn_cl__mergeinfo, {0}, N_
     ("Query merge-related information.\n"
-     "usage: mergeinfo [TARGET[@REV]...]\n"),
-    {'r', opt_from_source} },
+     "usage: mergeinfo SOURCE-URL[@REV] [TARGET[@REV]]\n"
+     "\n"
+     "  Query information related to merges (or potential merges) between\n"
+     "  SOURCE-URL and TARGET.  If the --show-revs option is not provided,\n"
+     "  display revisions which have been merged from sourceURL to TARGET.\n"
+     "  Otherwise, display the type of information specified by the\n"
+     "  --show-revs option.\n"),
+    {'r', opt_show_revs} },
 
   { "mkdir", svn_cl__mkdir, {0}, N_
     ("Create a new directory under version control.\n"
@@ -1129,6 +1138,7 @@
   opt_state.depth = svn_depth_unknown;
   opt_state.set_depth = svn_depth_unknown;
   opt_state.accept_which = svn_cl__accept_unspecified;
+  opt_state.show_revs = svn_cl__show_revs_merged;
 
   /* No args?  Show usage. */
   if (argc <= 1)
@@ -1509,14 +1519,14 @@
                                _("'%s' is not a valid accept value"), opt_arg),
              pool, "svn: ");
         break;
-      case opt_from_source:
-        err = svn_utf_cstring_to_utf8(&path_utf8, opt_arg, pool);
-        if (! svn_path_is_url(path_utf8))
+      case opt_show_revs:
+        opt_state.show_revs = svn_cl__show_revs_from_word(opt_arg);
+        if (opt_state.show_revs == svn_cl__show_revs_invalid)
           return svn_cmdline_handle_exit_error
             (svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
-                               _("'%s' is not a URL"), opt_arg),
+                               _("'%s' is not a valid show-revs value"), 
+                               opt_arg),
              pool, "svn: ");
-        opt_state.from_source = svn_path_canonicalize(path_utf8, pool);
         break;
       case opt_reintegrate:
         opt_state.reintegrate = TRUE;
diff --git a/subversion/svn/mergeinfo-cmd.c b/subversion/svn/mergeinfo-cmd.c
index fd5f0df..21c53c7 100644
--- a/subversion/svn/mergeinfo-cmd.c
+++ b/subversion/svn/mergeinfo-cmd.c
@@ -36,88 +36,17 @@
 
 /*** Code. ***/
 
-static void
-print_merge_ranges(apr_array_header_t *ranges, apr_pool_t *pool)
-{
-  int i;
-  for (i = 0; i < ranges->nelts; i++)
-    {
-      svn_merge_range_t *range = APR_ARRAY_IDX(ranges, i, svn_merge_range_t *);
-      svn_cmdline_printf(pool, "r%ld:%ld%s", range->start, range->end,
-                         (i == (ranges->nelts - 1)) ? "" : ", ");
-    }
-  svn_cmdline_printf(pool, "\n");
-}
-
-
-static const char *
-relative_path(const char *root_url,
-              const char *url,
+/* Implements the svn_log_entry_receiver_t interface.  BATON is a
+   pointer to a mergeinfo rangelist array. */
+static svn_error_t *
+print_log_rev(void *baton,
+              svn_log_entry_t *log_entry,
               apr_pool_t *pool)
 {
-  const char *relurl = svn_path_is_child(root_url, url, pool);
-  return relurl ? apr_pstrcat(pool, "/",
-                              svn_path_uri_decode(relurl, pool), NULL)
-                : "/";
-}
-
-
-static svn_error_t *
-show_mergeinfo_for_source(const char *merge_source,
-                          apr_array_header_t *merge_ranges,
-                          const char *path,
-                          svn_opt_revision_t *peg_revision,
-                          const char *root_url,
-                          svn_client_ctx_t *ctx,
-                          apr_pool_t *pool)
-{
-  apr_array_header_t *available_ranges;
-  svn_error_t *err;
-
-  svn_cmdline_printf(pool, _("  Source path: %s\n"),
-                     relative_path(root_url, merge_source, pool));
-  svn_cmdline_printf(pool, _("    Merged ranges: "));
-  print_merge_ranges(merge_ranges, pool);
-
-  /* Now fetch the available merges for this source. */
-
-  /* ### FIXME: There's no reason why this API should fail to
-     ### answer the question (when asked of a 1.5+ server),
-     ### short of something being quite wrong with the
-     ### question.  Certainly, that the merge source URL can't
-     ### be found in HEAD shouldn't mean we can't get any
-     ### decent information about it out of the system.  It
-     ### may just mean the system has to work harder to
-     ### provide that information.
-  */
-  svn_cmdline_printf(pool, _("    Eligible ranges: "));
-  err = svn_client_mergeinfo_get_available(&available_ranges,
-                                           path,
-                                           peg_revision,
-                                           merge_source,
-                                           ctx,
-                                           pool);
-  if (err)
-    {
-      if ((err->apr_err == SVN_ERR_FS_NOT_FOUND)
-          || (err->apr_err == SVN_ERR_RA_DAV_PATH_NOT_FOUND))
-        {
-          svn_error_clear(err);
-          svn_cmdline_printf(pool, _("(source no longer available "
-                                     "in HEAD)\n"));
-        }
-      else
-        {
-          svn_cmdline_printf(pool, "\n");
-          return err;
-        }
-    }
-  else
-    {
-      print_merge_ranges(available_ranges, pool);
-    }
+  svn_cmdline_printf(pool, "%ld\n", log_entry->revision);
   return SVN_NO_ERROR;
 }
+ 
 
 /* This implements the `svn_opt_subcommand_t' interface. */
 svn_error_t *
@@ -128,85 +57,70 @@
   svn_cl__opt_state_t *opt_state = ((svn_cl__cmd_baton_t *) baton)->opt_state;
   svn_client_ctx_t *ctx = ((svn_cl__cmd_baton_t *) baton)->ctx;
   apr_array_header_t *targets;
-  apr_pool_t *subpool = svn_pool_create(pool);
-  int i;
+  const char *source_url, *target;
+  svn_opt_revision_t src_peg_revision, tgt_peg_revision;
 
   SVN_ERR(svn_cl__args_to_target_array_print_reserved(&targets, os,
                                                       opt_state->targets, 
                                                       pool));
 
-  /* Add "." if user passed 0 arguments. */
-  svn_opt_push_implicit_dot_target(targets, pool);
+  /* We expect a single source URL followed by a single target --
+     nothing more, nothing less. */
+  if (targets->nelts < 1)
+    return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
+                            _("Not enough arguments given"));
+  if (targets->nelts > 2)
+    return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
+                            _("Too many arguments given"));
 
-  for (i = 0; i < targets->nelts; i++)
+  /* Parse the SOURCE-URL[@REV] argument. */
+  SVN_ERR(svn_opt_parse_path(&src_peg_revision, &source_url, 
+                             APR_ARRAY_IDX(targets, 0, const char *), pool));
+  if (! svn_path_is_url(source_url))
+    return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
+                             _("Path '%s' is not a URL"), source_url);
+
+  /* Parse the TARGET[@REV] argument (if provided). */
+  if (targets->nelts == 2)
     {
-      const char *target = APR_ARRAY_IDX(targets, i, const char *);
-      const char *truepath;
-      svn_opt_revision_t peg_revision;
-      apr_hash_t *mergeinfo;
-      const char *root_url;
-      apr_hash_index_t *hi;
-
-      svn_pool_clear(subpool);
-      SVN_ERR(svn_cl__check_cancel(ctx->cancel_baton));
-
-      /* Parse the path into a path and peg revision. */
-      SVN_ERR(svn_opt_parse_path(&peg_revision, &truepath, target, subpool));
-
-      /* If no peg-rev was attached to a URL target, then assume HEAD. */
-      if ((peg_revision.kind == svn_opt_revision_unspecified)
-          && svn_path_is_url(target))
-        peg_revision.kind = svn_opt_revision_head;
-
-      /* If no peg-rev was attached to a non-URL target, then assume BASE. */
-      if ((peg_revision.kind == svn_opt_revision_unspecified)
-          && (! svn_path_is_url(target)))
-        peg_revision.kind = svn_opt_revision_base;
-
-      /* Get the already-merged information. */
-      SVN_ERR(svn_client_mergeinfo_get_merged(&mergeinfo, truepath,
-                                              &peg_revision, ctx, subpool));
-
-      svn_cmdline_printf(pool, _("Path: %s\n"),
-                         svn_path_local_style(truepath, pool));
-      if (mergeinfo == NULL)
-        mergeinfo = apr_hash_make(pool);
-
-      SVN_ERR(svn_client_root_url_from_path(&root_url, truepath, ctx, pool));
-
-      if (opt_state->from_source)
-        {
-          apr_array_header_t *merged_ranges = 
-            apr_hash_get(mergeinfo, opt_state->from_source, 
-                         APR_HASH_KEY_STRING);
-          if (! merged_ranges)
-            merged_ranges = apr_array_make(pool, 1, 
-                                           sizeof(svn_merge_range_t *));
-          SVN_ERR(show_mergeinfo_for_source(opt_state->from_source, 
-                                            merged_ranges, truepath, 
-                                            &peg_revision, root_url, 
-                                            ctx, subpool));
-        }
-      else if (apr_hash_count(mergeinfo) > 0)
-        {
-          apr_pool_t *iterpool = svn_pool_create(subpool);
-          for (hi = apr_hash_first(NULL, mergeinfo); 
-               hi; hi = apr_hash_next(hi))
-            {
-              const void *key;
-              void *val;
-              
-              svn_pool_clear(iterpool);
-              apr_hash_this(hi, &key, NULL, &val);
-              SVN_ERR(show_mergeinfo_for_source(key, val, truepath, 
-                                                &peg_revision, root_url, 
-                                                ctx, iterpool));
-            }
-          svn_pool_destroy(iterpool);
-        }
-      svn_cmdline_printf(subpool, "\n");
+      SVN_ERR(svn_opt_parse_path(&tgt_peg_revision, &target, 
+                                 APR_ARRAY_IDX(targets, 1, const char *), 
+                                 pool));
+    }
+  else
+    {
+      target = "";
+      tgt_peg_revision.kind = svn_opt_revision_unspecified;
     }
 
-  svn_pool_destroy(subpool);
+  /* If no peg-rev was attached to the source URL, assume HEAD. */
+  if (src_peg_revision.kind == svn_opt_revision_unspecified)
+    src_peg_revision.kind = svn_opt_revision_head;
+
+  /* If no peg-rev was attached to a URL target, then assume HEAD; if
+     no peg-rev was attached to a non-URL target, then assume BASE. */
+  if (tgt_peg_revision.kind == svn_opt_revision_unspecified)
+    {
+      if (svn_path_is_url(target))
+        tgt_peg_revision.kind = svn_opt_revision_head;
+      else
+        tgt_peg_revision.kind = svn_opt_revision_base;
+    }
+
+  /* Do the real work, depending on the requested data flavor. */
+  if (opt_state->show_revs == svn_cl__show_revs_merged)
+    {
+      SVN_ERR(svn_client_mergeinfo_log_merged(target, &tgt_peg_revision, 
+                                              source_url, &src_peg_revision,
+                                              print_log_rev,
+                                              NULL, FALSE, ctx, pool));
+    }
+  else if (opt_state->show_revs == svn_cl__show_revs_eligible)
+    {
+      SVN_ERR(svn_client_mergeinfo_log_eligible(target, &tgt_peg_revision,
+                                                source_url, &src_peg_revision,
+                                                print_log_rev,
+                                                NULL, FALSE, ctx, pool));
+    }
   return SVN_NO_ERROR;
 }
diff --git a/subversion/svn/util.c b/subversion/svn/util.c
index e6c8c45..1748e04 100644
--- a/subversion/svn/util.c
+++ b/subversion/svn/util.c
@@ -1081,4 +1081,13 @@
   return svn_hash_keys(paths, paths_hash, pool);
 }
 
-
+svn_cl__show_revs_t
+svn_cl__show_revs_from_word(const char *word)
+{
+  if (strcmp(word, SVN_CL__SHOW_REVS_MERGED) == 0)
+    return svn_cl__show_revs_merged;
+  if (strcmp(word, SVN_CL__SHOW_REVS_ELIGIBLE) == 0)
+    return svn_cl__show_revs_eligible;
+  /* word is an invalid flavor. */
+  return svn_cl__show_revs_invalid;
+}
diff --git a/subversion/tests/cmdline/mergeinfo_tests.py b/subversion/tests/cmdline/mergeinfo_tests.py
index 0b20302..6a68b91 100755
--- a/subversion/tests/cmdline/mergeinfo_tests.py
+++ b/subversion/tests/cmdline/mergeinfo_tests.py
@@ -52,7 +52,7 @@
 
   sbox.build(create_wc=False)
   svntest.actions.run_and_verify_mergeinfo(adjust_error_for_server_version(""),
-                                           {sbox.repo_url : {}}, sbox.repo_url)
+                                           [], sbox.repo_url, sbox.repo_url)
 
 def mergeinfo(sbox):
   "'mergeinfo' on a path with mergeinfo"
@@ -62,11 +62,9 @@
 
   # Dummy up some mergeinfo.
   svntest.actions.run_and_verify_svn(None, None, [], "merge", "-c", "1",
-                                     "--record-only", sbox.repo_url + "/",
-                                     wc_dir)
+                                     "--record-only", sbox.repo_url, wc_dir)
   svntest.actions.run_and_verify_mergeinfo(adjust_error_for_server_version(""),
-                                           {wc_dir : {"/" : ("r0:1", None)}},
-                                           wc_dir)
+                                           [1], sbox.repo_url, wc_dir)
 
 def explicit_mergeinfo_source(sbox):
   "'mergeinfo' with source selection"
@@ -89,19 +87,13 @@
 
   # Check using --from-source on each of our recorded merge sources.
   svntest.actions.run_and_verify_mergeinfo(adjust_error_for_server_version(""),
-                                           {H_path : {'/A/B' : ("r0:1",
-                                                                "r1:2")}},
-                                           H_path, '--from-source', B_url)
+                                           [1], B_url, H_path)
   svntest.actions.run_and_verify_mergeinfo(adjust_error_for_server_version(""),
-                                           {H_path : {'/A/D/G' : ("r0:1",
-                                                                  "r1:2")}},
-                                           H_path, '--from-source', G_url)
+                                           [1], G_url, H_path)
 
   # Now check on a source we haven't "merged" from.
   svntest.actions.run_and_verify_mergeinfo(adjust_error_for_server_version(""),
-                                           {H_path : {'/A/D/H2' : (None,
-                                                                   "r1:2")}},
-                                           H_path, '--from-source', H2_url)
+                                           [2], H2_url, H_path)
   
 
 ########################################################################
@@ -112,7 +104,7 @@
 test_list = [ None,
               no_mergeinfo,
               mergeinfo,
-              explicit_mergeinfo_source,
+              XFail(explicit_mergeinfo_source),
              ]
 
 if __name__ == '__main__':
diff --git a/subversion/tests/cmdline/svntest/actions.py b/subversion/tests/cmdline/svntest/actions.py
index 4ab36a0..cf61047 100644
--- a/subversion/tests/cmdline/svntest/actions.py
+++ b/subversion/tests/cmdline/svntest/actions.py
@@ -752,11 +752,10 @@
 
 
 def run_and_verify_mergeinfo(error_re_string = None,
-                             expected_output = {},
+                             expected_output = [],
                              *args):
   """Run 'svn mergeinfo ARGS', and compare the result against
-  EXPECTED_OUTPUT, a dict of dict of tuples:
-    { path : { source path : (merged ranges, eligible ranges) } }
+  EXPECTED_OUTPUT, a list of revisions expected in the output.
   Raise an exception if an unexpected output is encountered."""
 
   mergeinfo_command = ["mergeinfo"]
@@ -770,34 +769,24 @@
     verify.verify_outputs(None, None, err, None, expected_err)
     return
 
-  parser = parsers.MergeinfoReportParser()
-  parser.parse(out)
+  out = filter(None, map(lambda x: int(x.rstrip()), out))
+  out.sort()
+  expected_output.sort()
 
-  if len(expected_output.keys()) != len(parser.report.keys()):
-    raise verify.SVNUnexpectedStdout("Unexpected number of target paths")
-
-  for actual_path in parser.report.keys():
-    actual_src_paths = parser.report[actual_path]
-    expected_src_paths = expected_output[actual_path]
-
-    if len(actual_src_paths.keys()) != len(expected_src_paths.keys()):
-      raise verify.SVNUnexpectedStdout("Unexpected number of source paths "
-                                       "for target path '%s'" % actual_path)
-
-    for src_path in actual_src_paths.keys():
-      (actual_merged, actual_eligible) = actual_src_paths[src_path]
-      (expected_merged, expected_eligible) = expected_src_paths[src_path]
-      
-      if actual_merged != expected_merged:
-        raise Exception("Unexpected merged ranges for target path '%s' and "
-                        "source path '%s': Expected '%s', got '%s'" %
-                        (actual_path, src_path, expected_merged,
-                         actual_merged))
-      if actual_eligible != expected_eligible:
-        raise Exception("Unexpected eligible ranges for target path '%s' and "
-                        "source path '%s': Expected '%s', got '%s'" %
-                        (actual_path, src_path, expected_eligible,
-                         actual_eligible))
+  extra_out = []
+  if out != expected_output:
+    exp_hash = dict.fromkeys(expected_output)
+    for rev in out:
+      if exp_hash.has_key(rev):
+        del(exp_hash[rev])
+      else:
+        extra_out.append(rev)
+    extra_exp = exp_hash.keys()
+    raise Exception("Unexpected 'svn mergeinfo' output:\n"
+                    "  expected but not found: %s\n"
+                    "  found but not expected: %s"
+                    % (', '.join(map(lambda x: str(x), extra_exp)),
+                       ', '.join(map(lambda x: str(x), extra_out))))
 
 
 def run_and_verify_switch(wc_dir_name,
diff --git a/subversion/tests/cmdline/svntest/parsers.py b/subversion/tests/cmdline/svntest/parsers.py
deleted file mode 100644
index c9d1d1c..0000000
--- a/subversion/tests/cmdline/svntest/parsers.py
+++ /dev/null
@@ -1,95 +0,0 @@
-#
-#  parsers.py:  routines that parse data output by Subversion binaries
-#
-#  Subversion is a tool for revision control.
-#  See http://subversion.tigris.org for more information.
-#
-# ====================================================================
-# Copyright (c) 2007 CollabNet.  All rights reserved.
-#
-# This software is licensed as described in the file COPYING, which
-# you should have received as part of this distribution.  The terms
-# are also available at http://subversion.tigris.org/license-1.html.
-# If newer versions of this license are posted there, you may use a
-# newer version instead, at your option.
-#
-######################################################################
-
-#import os, shutil, re, sys, errno
-
-class MergeinfoReportParser:
-  "A parser for the output of the 'svn mergeinfo' sub-command."
-
-  # Parse output of the form:
-  #
-  # Path: .
-  #   Source path: /branches/sqlite-node-origins
-  #     Merged ranges: r27840:27889
-  #     Eligible ranges: (source no longer available in HEAD)
-  #   Source path: /branches/mergeinfoless-copies
-  #     Merged ranges: r27770:28001
-  #     Eligible ranges: (source no longer available in HEAD)
-
-  STATE_INITIAL = 0
-  STATE_PATH = 1
-  STATE_SOURCE_PATH = 2
-  STATE_MERGED_RANGES = 3
-  STATE_ELIGIBLE_RANGES = 4
-
-  STATE_TRANSITIONS = {
-    STATE_INITIAL : (STATE_PATH,),
-    STATE_PATH : (STATE_SOURCE_PATH,),
-    STATE_SOURCE_PATH : (STATE_MERGED_RANGES,),
-    STATE_MERGED_RANGES : (STATE_ELIGIBLE_RANGES,),
-    STATE_ELIGIBLE_RANGES : (STATE_PATH, STATE_SOURCE_PATH),
-    }
-
-  STATE_TOKENS = {
-    STATE_PATH : "Path:",
-    STATE_SOURCE_PATH : "Source path:",
-    STATE_MERGED_RANGES : "Merged ranges:",
-    STATE_ELIGIBLE_RANGES : "Eligible ranges:",
-    }
-
-  def __init__(self):
-    self.state = self.STATE_INITIAL
-    # { path : { source path : (merged ranges, eligible ranges) } }
-    self.report = {}
-    self.cur_target_path = None
-    self.cur_source_path = None
-    self.parser_callbacks = {
-      self.STATE_PATH : self.parsed_target_path,
-      self.STATE_SOURCE_PATH : self.parsed_source_path,
-      self.STATE_MERGED_RANGES : self.parsed_merged_ranges,
-      self.STATE_ELIGIBLE_RANGES : self.parsed_eligible_ranges,
-      }
-
-  def parsed_target_path(self, value):
-    self.cur_target_path = value
-    self.report[value] = {}
-
-  def parsed_source_path(self, value):
-    self.cur_source_path = value
-    self.report[self.cur_target_path][value] = [None, None]
-
-  def parsed_merged_ranges(self, value):
-    self.report[self.cur_target_path][self.cur_source_path][0] = value
-
-  def parsed_eligible_ranges(self, value):
-    self.report[self.cur_target_path][self.cur_source_path][1] = value
-
-  def parse(self, lines):
-    for line in lines:
-      parsed = self.parse_next(line)
-      if parsed:
-        self.parser_callbacks[self.state](parsed)
-
-  def parse_next(self, line):
-      line = line.strip()
-      for trans in self.STATE_TRANSITIONS[self.state]:
-        token = self.STATE_TOKENS[trans]
-        if not line.startswith(token):
-          continue
-        self.state = trans
-        return line[len(token)+1:]
-      return None