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,
©from_path, ©from_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