You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@subversion.apache.org by ju...@apache.org on 2012/09/21 22:42:09 UTC

svn commit: r1388673 - in /subversion/trunk/subversion: include/private/svn_client_private.h libsvn_client/merge.c svn/main.c svn/mergeinfo-cmd.c

Author: julianfoad
Date: Fri Sep 21 20:42:09 2012
New Revision: 1388673

URL: http://svn.apache.org/viewvc?rev=1388673&view=rev
Log:
Teach 'svn mergeinfo' to print an ASCII-art diagram summarizing the state of
merging between the two branches.

This mode will now be used by default, and the --show-revs option can still
be used to get a plain list of revision numbers.  This is a start, and
concentrates on "full" merges; it is not yet very useful where
cherry-picking is the main form of merge, such as on a release branch.

* subversion/include/private/svn_client_private.h,
  subversion/libsvn_client/merge.c
  (svn_client__symmetric_merge_t): Add the 'target' location.
  (svn_client__find_symmetric_merge_no_wc,
   svn_client__symmetric_merge_get_locations): New functions.
  (find_symmetric_merge): Accept a target branch that is not a WC.

* subversion/svn/main.c
  (svn_cl__cmd_table): Update the help for 'svn mergeinfo'.
  (sub_main): Change the default value of the show-revs option to 'invalid'
    so we can distinguish when the option has not been given.

* subversion/svn/mergeinfo-cmd.c
  (mergeinfo_diagram, mergeinfo_summary): New functions.
  (svn_cl__mergeinfo): Call mergeinfo_summary() if no show-revs option was
    given.

Modified:
    subversion/trunk/subversion/include/private/svn_client_private.h
    subversion/trunk/subversion/libsvn_client/merge.c
    subversion/trunk/subversion/svn/main.c
    subversion/trunk/subversion/svn/mergeinfo-cmd.c

Modified: subversion/trunk/subversion/include/private/svn_client_private.h
URL: http://svn.apache.org/viewvc/subversion/trunk/subversion/include/private/svn_client_private.h?rev=1388673&r1=1388672&r2=1388673&view=diff
==============================================================================
--- subversion/trunk/subversion/include/private/svn_client_private.h (original)
+++ subversion/trunk/subversion/include/private/svn_client_private.h Fri Sep 21 20:42:09 2012
@@ -202,6 +202,45 @@ svn_client__find_symmetric_merge(svn_cli
                                  apr_pool_t *result_pool,
                                  apr_pool_t *scratch_pool);
 
+/* Find out what kind of symmetric merge would be needed, when the target
+ * is only known as a repository location rather than a WC.
+ *
+ * Like svn_client__find_symmetric_merge() except that SOURCE_PATH_OR_URL @
+ * SOURCE_REVISION should refer to a repository location and not a WC.
+ *
+ * ### The result, *MERGE_P, may not be suitable for passing to
+ * svn_client__do_symmetric_merge().  The target WC state would not be
+ * checked (as in the ALLOW_* flags).  We should resolve this problem:
+ * perhaps add the allow_* params here, or provide another way of setting
+ * them; and perhaps ensure __do_...() will accept the result iff given a
+ * WC that matches the stored target location.
+ */
+svn_error_t *
+svn_client__find_symmetric_merge_no_wc(
+                                 svn_client__symmetric_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);
+
+/* Set *YCA, *BASE, *RIGHT, *TARGET to the repository locations of the
+ * youngest common ancestor of the branches, the base chosen for 3-way
+ * merge, the right-hand side of the source diff, and the target WC.
+ *
+ * Any of the output pointers may be NULL if not wanted.
+ */
+svn_error_t *
+svn_client__symmetric_merge_get_locations(
+                                svn_client__pathrev_t **yca,
+                                svn_client__pathrev_t **base,
+                                svn_client__pathrev_t **right,
+                                svn_client__pathrev_t **target,
+                                const svn_client__symmetric_merge_t *merge,
+                                apr_pool_t *result_pool);
+
 /* Perform a symmetric merge.
  *
  * Merge according to MERGE into the WC at TARGET_WCPATH.

Modified: subversion/trunk/subversion/libsvn_client/merge.c
URL: http://svn.apache.org/viewvc/subversion/trunk/subversion/libsvn_client/merge.c?rev=1388673&r1=1388672&r2=1388673&view=diff
==============================================================================
--- subversion/trunk/subversion/libsvn_client/merge.c (original)
+++ subversion/trunk/subversion/libsvn_client/merge.c Fri Sep 21 20:42:09 2012
@@ -11443,14 +11443,23 @@ find_symmetric_merge(svn_client__pathrev
                                           svn_mergeinfo_inherited,
                                           FALSE /* squelch_incapable */,
                                           scratch_pool));
-  SVN_ERR(svn_client__get_wc_or_repos_mergeinfo(&s_t->target_mergeinfo,
-                                                NULL /* inherited */,
-                                                NULL /* from_repos */,
-                                                FALSE /* repos_only */,
-                                                svn_mergeinfo_inherited,
-                                                s_t->target_ra_session,
-                                                s_t->target->abspath,
-                                                ctx, scratch_pool));
+  if (! s_t->target->abspath)
+    SVN_ERR(svn_client__get_repos_mergeinfo(&s_t->target_mergeinfo,
+                                            s_t->target_ra_session,
+                                            s_t->target->loc.url,
+                                            s_t->target->loc.rev,
+                                            svn_mergeinfo_inherited,
+                                            FALSE /* squelch_incapable */,
+                                            scratch_pool));
+  else
+    SVN_ERR(svn_client__get_wc_or_repos_mergeinfo(&s_t->target_mergeinfo,
+                                                  NULL /* inherited */,
+                                                  NULL /* from_repos */,
+                                                  FALSE /* repos_only */,
+                                                  svn_mergeinfo_inherited,
+                                                  s_t->target_ra_session,
+                                                  s_t->target->abspath,
+                                                  ctx, scratch_pool));
 
   /* Get the location-history of each branch. */
   s_t->source_branch.tip = s_t->source;
@@ -11497,11 +11506,53 @@ find_symmetric_merge(svn_client__pathrev
 /* Details of a symmetric merge. */
 struct svn_client__symmetric_merge_t
 {
-  svn_client__pathrev_t *yca, *base, *mid, *right;
+  svn_client__pathrev_t *yca, *base, *mid, *right, *target;
   svn_boolean_t allow_mixed_rev, allow_local_mods, allow_switched_subtrees;
 };
 
 svn_error_t *
+svn_client__find_symmetric_merge_no_wc(
+                                 svn_client__symmetric_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__symmetric_merge_t *merge = apr_palloc(result_pool, sizeof(*merge));
+
+  /* Source */
+  SVN_ERR(svn_client__ra_session_from_path2(
+            &s_t->source_ra_session, &s_t->source,
+            source_path_or_url, NULL, source_revision, source_revision,
+            ctx, result_pool));
+
+  /* Target */
+  SVN_ERR(svn_client__ra_session_from_path2(
+            &s_t->target_ra_session, &target_loc,
+            target_path_or_url, NULL, target_revision, target_revision,
+            ctx, result_pool));
+  s_t->target = apr_palloc(scratch_pool, sizeof(*s_t->target));
+  s_t->target->kind = svn_node_none;
+  s_t->target->abspath = NULL;  /* indicate the target is not a WC */
+  s_t->target->loc = *target_loc;
+
+  SVN_ERR(find_symmetric_merge(&merge->base, &merge->mid, s_t,
+                               ctx, result_pool, scratch_pool));
+
+  merge->right = s_t->source;
+  merge->target = &s_t->target->loc;
+  merge->yca = s_t->yca;
+  *merge_p = merge;
+
+  return SVN_NO_ERROR;
+}
+
+svn_error_t *
 svn_client__find_symmetric_merge(svn_client__symmetric_merge_t **merge_p,
                                  const char *source_path_or_url,
                                  const svn_opt_revision_t *source_revision,
@@ -11742,4 +11793,24 @@ svn_client__symmetric_merge_is_reintegra
         const svn_client__symmetric_merge_t *merge)
 {
   return merge->mid != NULL;
+}
+
+svn_error_t *
+svn_client__symmetric_merge_get_locations(
+                                svn_client__pathrev_t **yca,
+                                svn_client__pathrev_t **base,
+                                svn_client__pathrev_t **right,
+                                svn_client__pathrev_t **target,
+                                const svn_client__symmetric_merge_t *merge,
+                                apr_pool_t *result_pool)
+{
+  if (yca)
+    *yca = svn_client__pathrev_dup(merge->yca, result_pool);
+  if (base)
+    *base = svn_client__pathrev_dup(merge->base, result_pool);
+  if (right)
+    *right = svn_client__pathrev_dup(merge->right, result_pool);
+  if (target)
+    *target = svn_client__pathrev_dup(merge->target, result_pool);
+  return SVN_NO_ERROR;
 }
\ No newline at end of file

Modified: subversion/trunk/subversion/svn/main.c
URL: http://svn.apache.org/viewvc/subversion/trunk/subversion/svn/main.c?rev=1388673&r1=1388672&r2=1388673&view=diff
==============================================================================
--- subversion/trunk/subversion/svn/main.c (original)
+++ subversion/trunk/subversion/svn/main.c Fri Sep 21 20:42:09 2012
@@ -1036,13 +1036,19 @@ const svn_opt_subcommand_desc2_t svn_cl_
 
   { "mergeinfo", svn_cl__mergeinfo, {0}, N_
     ("Display merge-related information.\n"
-     "usage: mergeinfo SOURCE[@REV] [TARGET[@REV]]\n"
-     "\n"
-     "  Display information related to merges (or potential merges) between\n"
-     "  SOURCE and TARGET (default: '.').  Display the type of information\n"
-     "  specified by the --show-revs option.  If --show-revs isn't passed,\n"
-     "  it defaults to --show-revs='merged'.\n"
+     "usage: 1. mergeinfo SOURCE[@REV] [TARGET[@REV]]\n"
+     "       2. mergeinfo --show-revs=merged SOURCE[@REV] [TARGET[@REV]]\n"
+     "       3. mergeinfo --show-revs=eligible SOURCE[@REV] [TARGET[@REV]]\n"
+     "\n"
+     "  1. Display the following information about merges between SOURCE and\n"
+     "     TARGET:\n"
+     "       the youngest common ancestor;\n"
+     "       the latest full merge in either direction, and thus the\n"
+     "         base that will be used for the next full merge.\n"
+     "  2. Print the revision numbers on SOURCE that have been merged to TARGET.\n"
+     "  3. Print the revision numbers on SOURCE that have NOT been merged to TARGET.\n"
      "\n"
+     "  The default TARGET is the current working directory ('.').\n"
      "  If --revision (-r) is provided, filter the displayed information to\n"
      "  show only that which is associated with the revisions within the\n"
      "  specified range.  Revision numbers, dates, and the 'HEAD' keyword are\n"
@@ -1670,7 +1676,7 @@ sub_main(int argc, const char *argv[], a
   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;
+  opt_state.show_revs = svn_cl__show_revs_invalid;
 
   /* No args?  Show usage. */
   if (argc <= 1)

Modified: subversion/trunk/subversion/svn/mergeinfo-cmd.c
URL: http://svn.apache.org/viewvc/subversion/trunk/subversion/svn/mergeinfo-cmd.c?rev=1388673&r1=1388672&r2=1388673&view=diff
==============================================================================
--- subversion/trunk/subversion/svn/mergeinfo-cmd.c (original)
+++ subversion/trunk/subversion/svn/mergeinfo-cmd.c Fri Sep 21 20:42:09 2012
@@ -37,6 +37,7 @@
 #include "cl.h"
 
 #include "svn_private_config.h"
+#include "private/svn_client_private.h"
 
 
 /*** Code. ***/
@@ -55,6 +56,194 @@ 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(svn_client__pathrev_t *yca,
+                  svn_client__pathrev_t *base,
+                  svn_client__pathrev_t *right,
+                  svn_client__pathrev_t *target,
+                  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) */
+  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] = "     -----";
+    }
+
+  /* An ellipsis, because we don't show information about earlier merges */
+    {
+      g[2][1] = "| ... |---";
+      g[3][1] = "          ";
+      g[4][1] = "          ";
+      g[5][1] = "| ... |---";
+    }
+
+  /* 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 ? apr_psprintf(pool, "%-8ld", target->rev)
+                             : "WC      ";
+    }
+
+  /* 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] && (strlen(g[row][col]) > col_width[col]))
+            col_width[col] = strlen(g[row][col]);
+        }
+    }
+
+  /* Column headings */
+  SVN_ERR(svn_cmdline_fputs(
+            _("    youngest          last               repos.\n"
+              "    common            full     tip of    path of\n"
+              "    ancestor          merge    branch    branch\n"
+              "\n"),
+            stdout, pool));
+
+  /* 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_client__pathrev_relpath(right, pool)));
+      if (row == 5)
+        SVN_ERR(svn_cmdline_printf(pool, "  %s",
+                svn_client__pathrev_relpath(target, 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)
+{
+  svn_client__symmetric_merge_t *the_merge;
+  svn_client__pathrev_t *yca, *base, *right, *target;
+  svn_boolean_t target_is_wc, reintegrate_like;
+
+  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(svn_client__find_symmetric_merge(
+              &the_merge,
+              source_path_or_url, source_revision,
+              target_path_or_url,
+              TRUE, TRUE, TRUE,  /* allow_* */
+              ctx, pool, pool));
+  else
+    SVN_ERR(svn_client__find_symmetric_merge_no_wc(
+              &the_merge,
+              source_path_or_url, source_revision,
+              target_path_or_url, target_revision,
+              ctx, pool, pool));
+
+  SVN_ERR(svn_client__symmetric_merge_get_locations(
+            &yca, &base, &right, &target, the_merge, pool));
+  reintegrate_like = svn_client__symmetric_merge_is_reintegrate_like(the_merge);
+
+  SVN_ERR(mergeinfo_diagram(yca, base, right, target,
+                            target_is_wc, reintegrate_like,
+                            pool));
+
+  return SVN_NO_ERROR;
+}
+
 /* This implements the `svn_opt_subcommand_t' interface. */
 svn_error_t *
 svn_cl__mergeinfo(apr_getopt_t *os,
@@ -140,5 +329,11 @@ svn_cl__mergeinfo(apr_getopt_t *os,
                                         TRUE, depth, NULL, ctx,
                                         pool));
     }
+  else
+    {
+      SVN_ERR(mergeinfo_summary(source, &src_peg_revision,
+                                target, &tgt_peg_revision,
+                                ctx, pool));
+    }
   return SVN_NO_ERROR;
 }