summaryrefslogtreecommitdiff
path: root/subversion/svn/mergeinfo-cmd.c
diff options
context:
space:
mode:
Diffstat (limited to 'subversion/svn/mergeinfo-cmd.c')
-rw-r--r--subversion/svn/mergeinfo-cmd.c255
1 files changed, 237 insertions, 18 deletions
diff --git a/subversion/svn/mergeinfo-cmd.c b/subversion/svn/mergeinfo-cmd.c
index 5875e2d..41edcda 100644
--- a/subversion/svn/mergeinfo-cmd.c
+++ b/subversion/svn/mergeinfo-cmd.c
@@ -55,6 +55,189 @@ print_log_rev(void *baton,
return SVN_NO_ERROR;
}
+/* Draw a diagram (by printing text to the console) summarizing the state
+ * of merging between two branches, given the merge description
+ * indicated by YCA, BASE, RIGHT, TARGET, REINTEGRATE_LIKE. */
+static svn_error_t *
+mergeinfo_diagram(const char *yca_url,
+ const char *base_url,
+ const char *right_url,
+ const char *target_url,
+ svn_revnum_t yca_rev,
+ svn_revnum_t base_rev,
+ svn_revnum_t right_rev,
+ svn_revnum_t target_rev,
+ const char *repos_root_url,
+ svn_boolean_t target_is_wc,
+ svn_boolean_t reintegrate_like,
+ apr_pool_t *pool)
+{
+ /* The graph occupies 4 rows of text, and the annotations occupy
+ * another 2 rows above and 2 rows below. The graph is constructed
+ * from left to right in discrete sections ("columns"), each of which
+ * can have a different width (measured in characters). Each element in
+ * the array is either a text string of the appropriate width, or can
+ * be NULL to draw a blank cell. */
+#define ROWS 8
+#define COLS 4
+ const char *g[ROWS][COLS] = {{0}};
+ int col_width[COLS];
+ int row, col;
+
+ /* The YCA (that is, the branching point). And an ellipsis, because we
+ * don't show information about earlier merges */
+ g[0][0] = apr_psprintf(pool, " %-8ld ", yca_rev);
+ g[1][0] = " | ";
+ if (strcmp(yca_url, right_url) == 0)
+ {
+ g[2][0] = "-------| |--";
+ g[3][0] = " \\ ";
+ g[4][0] = " \\ ";
+ g[5][0] = " --| |--";
+ }
+ else if (strcmp(yca_url, target_url) == 0)
+ {
+ g[2][0] = " --| |--";
+ g[3][0] = " / ";
+ g[4][0] = " / ";
+ g[5][0] = "-------| |--";
+ }
+ else
+ {
+ g[2][0] = " --| |--";
+ g[3][0] = "... / ";
+ g[4][0] = " \\ ";
+ g[5][0] = " --| |--";
+ }
+
+ /* The last full merge */
+ if ((base_rev > yca_rev) && reintegrate_like)
+ {
+ g[2][2] = "---------";
+ g[3][2] = " / ";
+ g[4][2] = " / ";
+ g[5][2] = "---------";
+ g[6][2] = "| ";
+ g[7][2] = apr_psprintf(pool, "%-8ld ", base_rev);
+ }
+ else if (base_rev > yca_rev)
+ {
+ g[0][2] = apr_psprintf(pool, "%-8ld ", base_rev);
+ g[1][2] = "| ";
+ g[2][2] = "---------";
+ g[3][2] = " \\ ";
+ g[4][2] = " \\ ";
+ g[5][2] = "---------";
+ }
+ else
+ {
+ g[2][2] = "---------";
+ g[3][2] = " ";
+ g[4][2] = " ";
+ g[5][2] = "---------";
+ }
+
+ /* The tips of the branches */
+ {
+ g[0][3] = apr_psprintf(pool, "%-8ld", right_rev);
+ g[1][3] = "| ";
+ g[2][3] = "- ";
+ g[3][3] = " ";
+ g[4][3] = " ";
+ g[5][3] = "- ";
+ g[6][3] = "| ";
+ g[7][3] = target_is_wc ? "WC "
+ : apr_psprintf(pool, "%-8ld", target_rev);
+ }
+
+ /* Find the width of each column, so we know how to print blank cells */
+ for (col = 0; col < COLS; col++)
+ {
+ col_width[col] = 0;
+ for (row = 0; row < ROWS; row++)
+ {
+ if (g[row][col] && ((int)strlen(g[row][col]) > col_width[col]))
+ col_width[col] = (int)strlen(g[row][col]);
+ }
+ }
+
+ /* Column headings */
+ SVN_ERR(svn_cmdline_printf(pool,
+ " %s\n"
+ " | %s\n"
+ " | | %s\n"
+ " | | | %s\n"
+ "\n",
+ _("youngest common ancestor"), _("last full merge"),
+ _("tip of branch"), _("repository path")));
+
+ /* Print the diagram, row by row */
+ for (row = 0; row < ROWS; row++)
+ {
+ SVN_ERR(svn_cmdline_fputs(" ", stdout, pool));
+ for (col = 0; col < COLS; col++)
+ {
+ if (g[row][col])
+ {
+ SVN_ERR(svn_cmdline_fputs(g[row][col], stdout, pool));
+ }
+ else
+ {
+ /* Print <column-width> spaces */
+ SVN_ERR(svn_cmdline_printf(pool, "%*s", col_width[col], ""));
+ }
+ }
+ if (row == 2)
+ SVN_ERR(svn_cmdline_printf(pool, " %s",
+ svn_uri_skip_ancestor(repos_root_url, right_url, pool)));
+ if (row == 5)
+ SVN_ERR(svn_cmdline_printf(pool, " %s",
+ svn_uri_skip_ancestor(repos_root_url, target_url, pool)));
+ SVN_ERR(svn_cmdline_fputs("\n", stdout, pool));
+ }
+
+ return SVN_NO_ERROR;
+}
+
+/* Display a summary of the state of merging between the two branches
+ * SOURCE_PATH_OR_URL@SOURCE_REVISION and
+ * TARGET_PATH_OR_URL@TARGET_REVISION. */
+static svn_error_t *
+mergeinfo_summary(
+ 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 *pool)
+{
+ const char *yca_url, *base_url, *right_url, *target_url;
+ svn_revnum_t yca_rev, base_rev, right_rev, target_rev;
+ const char *repos_root_url;
+ svn_boolean_t target_is_wc, is_reintegration;
+
+ 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);
+ SVN_ERR(svn_client_get_merging_summary(
+ &is_reintegration,
+ &yca_url, &yca_rev,
+ &base_url, &base_rev,
+ &right_url, &right_rev,
+ &target_url, &target_rev,
+ &repos_root_url,
+ source_path_or_url, source_revision,
+ target_path_or_url, target_revision,
+ ctx, pool, pool));
+
+ SVN_ERR(mergeinfo_diagram(yca_url, base_url, right_url, target_url,
+ yca_rev, base_rev, right_rev, target_rev,
+ repos_root_url, target_is_wc, is_reintegration,
+ pool));
+
+ return SVN_NO_ERROR;
+}
+
/* This implements the `svn_opt_subcommand_t' interface. */
svn_error_t *
svn_cl__mergeinfo(apr_getopt_t *os,
@@ -66,28 +249,24 @@ svn_cl__mergeinfo(apr_getopt_t *os,
apr_array_header_t *targets;
const char *source, *target;
svn_opt_revision_t src_peg_revision, tgt_peg_revision;
+ svn_opt_revision_t *src_start_revision, *src_end_revision;
/* Default to depth empty. */
- svn_depth_t depth = opt_state->depth == svn_depth_unknown
- ? svn_depth_empty : opt_state->depth;
+ svn_depth_t depth = (opt_state->depth == svn_depth_unknown)
+ ? svn_depth_empty : opt_state->depth;
SVN_ERR(svn_cl__args_to_target_array_print_reserved(&targets, os,
opt_state->targets,
ctx, FALSE, pool));
- /* We expect a single source URL followed by a single target --
- nothing more, nothing less. */
+ /* Parse the arguments: SOURCE[@REV] optionally followed by TARGET[@REV]. */
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"));
-
- /* Parse the SOURCE-URL[@REV] argument. */
SVN_ERR(svn_opt_parse_path(&src_peg_revision, &source,
APR_ARRAY_IDX(targets, 0, const char *), pool));
-
- /* Parse the TARGET[@REV] argument (if provided). */
if (targets->nelts == 2)
{
SVN_ERR(svn_opt_parse_path(&tgt_peg_revision, &target,
@@ -101,11 +280,15 @@ svn_cl__mergeinfo(apr_getopt_t *os,
}
/* If no peg-rev was attached to the source URL, assume HEAD. */
+ /* ### But what if SOURCE is a WC path not a URL -- shouldn't we then use
+ * BASE (but not WORKING: that would be inconsistent with 'svn merge')? */
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. */
+ /* ### But we would like to be able to examine a working copy with an
+ uncommitted merge in it, so change this to use WORKING not BASE? */
if (tgt_peg_revision.kind == svn_opt_revision_unspecified)
{
if (svn_path_is_url(target))
@@ -114,22 +297,58 @@ svn_cl__mergeinfo(apr_getopt_t *os,
tgt_peg_revision.kind = svn_opt_revision_base;
}
+ src_start_revision = &(opt_state->start_revision);
+ if (opt_state->end_revision.kind == svn_opt_revision_unspecified)
+ src_end_revision = src_start_revision;
+ else
+ src_end_revision = &(opt_state->end_revision);
+
/* 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(TRUE, target, &tgt_peg_revision,
- source, &src_peg_revision,
- print_log_rev, NULL,
- TRUE, depth, NULL, ctx,
- pool));
+ apr_array_header_t *revprops;
+
+ /* We need only revisions number, not revision properties. */
+ revprops = apr_array_make(pool, 0, sizeof(const char *));
+
+ SVN_ERR(svn_client_mergeinfo_log2(TRUE, target, &tgt_peg_revision,
+ source, &src_peg_revision,
+ src_start_revision,
+ src_end_revision,
+ print_log_rev, NULL,
+ TRUE, depth, revprops, ctx,
+ pool));
}
else if (opt_state->show_revs == svn_cl__show_revs_eligible)
{
- SVN_ERR(svn_client_mergeinfo_log(FALSE, target, &tgt_peg_revision,
- source, &src_peg_revision,
- print_log_rev, NULL,
- TRUE, depth, NULL, ctx,
- pool));
+ apr_array_header_t *revprops;
+
+ /* We need only revisions number, not revision properties. */
+ revprops = apr_array_make(pool, 0, sizeof(const char *));
+
+ SVN_ERR(svn_client_mergeinfo_log2(FALSE, target, &tgt_peg_revision,
+ source, &src_peg_revision,
+ src_start_revision,
+ src_end_revision,
+ print_log_rev, NULL,
+ TRUE, depth, revprops, ctx,
+ pool));
+ }
+ else
+ {
+ if ((opt_state->start_revision.kind != svn_opt_revision_unspecified)
+ || (opt_state->end_revision.kind != svn_opt_revision_unspecified))
+ return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
+ _("--revision (-r) option valid only with "
+ "--show-revs option"));
+ if (opt_state->depth != svn_depth_unknown)
+ return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
+ _("Depth specification options valid only "
+ "with --show-revs option"));
+
+ SVN_ERR(mergeinfo_summary(source, &src_peg_revision,
+ target, &tgt_peg_revision,
+ ctx, pool));
}
return SVN_NO_ERROR;
}