Gabriela Gibson wrote on 13 October:
> my branch has grown into a veritable forest, and so, I thought that
> it would be convenient to present the net code changes
... and the discussion went on to address how to use "svn diff" in the right
way to show such changes, which is not exactly obvious. The best way is to go
and investigate your merge history and then choose specify the left hand side
of the diff as the revision on trunk which you last caught up to. Is that
really the best we can do? No.
This requirement is fairly basic and comes up quite often -- I have recently
heard from both customers and colleagues wanting to know how to do it.
I think we should have a built-in way to say "show me the diff of this branch
against the parent branch, specifically against the latest catch-up point on
the parent branch". The attached patch implements this, using
'-g'/'--use-merge-info' to trigger it:
cd my-branch-wc
svn diff -g ^/subversion/trunk .
What does everyone think of the concept? The user interface? This patch is by
no means a complete solution, but simply to promote discussion.
- Julian
Teach 'svn diff' to show a diff against the last merge base. Do this when
the '-g' (--use-merge-info) option is given.
* subversion/svn/diff-cmd.c
(find_last_merge_base): New function.
(svn_cl__diff): If requested, look up and diff against the last merge base.
* subversion/svn/svn.c
(svn_cl__cmd_table): Let 'svn diff' take the '-g' option.
--This line, and those below, will be ignored--
Index: subversion/svn/diff-cmd.c
===================================================================
--- subversion/svn/diff-cmd.c (revision 1534663)
+++ subversion/svn/diff-cmd.c (working copy)
@@ -160,12 +160,35 @@ summarize_regular(const svn_client_diff_
summary->prop_changed ? 'M' : ' ',
path));
return svn_cmdline_fflush(stdout);
}
+/* Set *BASE_URL and *BASE_REV to the base of the last complete merge
+ * between the source branch SOURCE_PATH_OR_URL@SOURCE_REVISION and
+ * the target branch TARGET_PATH_OR_URL@TARGET_REVISION. */
+static svn_error_t *
+find_last_merge_base(const char **base_url,
+ svn_revnum_t *base_rev,
+ 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_ERR(svn_client_get_merging_summary(
+ NULL, NULL, NULL,
+ base_url, base_rev,
+ NULL, NULL, NULL, NULL, NULL,
+ source_path_or_url, source_revision,
+ target_path_or_url, target_revision,
+ ctx, pool, pool));
+ return SVN_NO_ERROR;
+}
+
/* An svn_opt_subcommand_t to handle the 'diff' command.
This implements the `svn_opt_subcommand_t' interface. */
svn_error_t *
svn_cl__diff(apr_getopt_t *os,
void *baton,
apr_pool_t *pool)
@@ -335,12 +358,35 @@ svn_cl__diff(apr_getopt_t *os,
|| (opt_state->end_revision.kind != svn_opt_revision_base
&& opt_state->end_revision.kind != svn_opt_revision_working))
pegged_diff = TRUE;
}
+ /* If requested, diff against the last merge base */
+ if (opt_state->use_merge_history)
+ {
+ svn_revnum_t base_rev;
+
+ /* ### Trying to check it's a suitable form of diff invocation; not
+ * sure if this check is perfect. */
+ if (! (*old_target || *new_target))
+ {
+ return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
+ _("'--use-merge-info' option only valid "
+ "with two-URL forms of diff"));
+ }
+
+ SVN_ERR(find_last_merge_base(&old_target, &base_rev,
+ old_target, &opt_state->start_revision,
+ new_target, &opt_state->end_revision,
+ ctx, pool));
+ opt_state->start_revision.kind = svn_opt_revision_number;
+ opt_state->start_revision.value.number = base_rev;
+ SVN_DBG(("Diffing against %s@%ld", old_target, opt_state->start_revision.value.number));
+ }
+
/* Should we ignore the content-type when deciding what to diff? */
if (opt_state->force)
{
ignore_content_type = TRUE;
}
else
@@ -384,12 +430,15 @@ svn_cl__diff(apr_getopt_t *os,
new_target,
svn_relpath_canonicalize(path, iterpool),
iterpool);
else
target2 = svn_dirent_join(new_target, path, iterpool);
+ SVN_DBG(("diff %s@%ld %s@%ld",
+ target1, opt_state->start_revision.value.number,
+ target2, opt_state->end_revision.value.number));
if (opt_state->diff.summarize)
{
summarize_baton.anchor = target1;
SVN_ERR(svn_client_diff_summarize2(
target1,
Index: subversion/svn/svn.c
===================================================================
--- subversion/svn/svn.c (revision 1534663)
+++ subversion/svn/svn.c (working copy)
@@ -602,18 +602,22 @@ const svn_opt_subcommand_desc2_t svn_cl_
" targets. Revisions cannot be specified for unversioned targets.\n"
" Both targets must be of the same node kind (file or directory).\n"
" Diffing unversioned targets against URL targets is not supported.\n"
"\n"
" 4. Shorthand for 'svn diff --old=OLD-URL[@OLDREV] --new=NEW-URL[@NEWREV]'\n"
" 5. Shorthand for 'svn diff --old=OLD-URL[@OLDREV] --new=NEW-PATH[@NEWREV]'\n"
- " 6. Shorthand for 'svn diff --old=OLD-PATH[@OLDREV] --new=NEW-URL[@NEWREV]'\n"),
+ " 6. Shorthand for 'svn diff --old=OLD-PATH[@OLDREV] --new=NEW-URL[@NEWREV]'\n"
+ "\n"
+ " The --use-merge-info option compares the 'new' target against the base\n"
+ " of the last complete merge between old and new.\n"),
{'r', 'c', opt_old_cmd, opt_new_cmd, 'N', opt_depth, opt_diff_cmd,
opt_internal_diff, 'x', opt_no_diff_added, opt_no_diff_deleted,
opt_ignore_properties, opt_properties_only,
opt_show_copies_as_adds, opt_notice_ancestry, opt_summarize, opt_changelist,
- opt_force, opt_xml, opt_use_git_diff_format, opt_patch_compatible} },
+ opt_force, opt_xml, opt_use_git_diff_format, opt_patch_compatible, 'g'},
+ {{'g', N_("diff against last merge base")}} },
{ "export", svn_cl__export, {0}, N_
("Create an unversioned copy of a tree.\n"
"usage: 1. export [-r REV] URL[@PEGREV] [PATH]\n"
" 2. export [-r REV] PATH1[@PEGREV] [PATH2]\n"
"\n"
" 1. Exports a clean directory tree from the repository specified by\n"