Skip to content

Commit ef3c243

Browse files
committed
range-diff: optionally include merge commits' diffs in the analysis
The `git log` command already offers support for including diffs for merges, via the `--diff-merges=<format>` option. Let's add corresponding support for `git range-diff`, too. This makes it more convenient to spot differences between commit ranges that contain merges. This is especially true in scenarios with non-trivial merges, i.e. merges introducing changes other than, or in addition to, what merge ORT would have produced. Merging a topic branch that changes a function signature into a branch that added a caller of that function, for example, would require the merge commit itself to adjust that caller to the modified signature. In my code reviews, I found the `--diff-merges=remerge` option particularly useful. Signed-off-by: Johannes Schindelin <[email protected]>
1 parent f94bfa1 commit ef3c243

File tree

5 files changed

+51
-6
lines changed

5 files changed

+51
-6
lines changed

Documentation/git-range-diff.txt

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ SYNOPSIS
1010
[verse]
1111
'git range-diff' [--color=[<when>]] [--no-color] [<diff-options>]
1212
[--no-dual-color] [--creation-factor=<factor>]
13-
[--left-only | --right-only]
13+
[--left-only | --right-only] [--diff-merges=<format>]
1414
( <range1> <range2> | <rev1>...<rev2> | <base> <rev1> <rev2> )
1515
[[--] <path>...]
1616

@@ -81,6 +81,17 @@ to revert to color all lines according to the outer diff markers
8181
Suppress commits that are missing from the second specified range
8282
(or the "right range" when using the `<rev1>...<rev2>` format).
8383

84+
--diff-merges=<format>::
85+
Instead of ignoring merge commits, generate diffs for them using the
86+
corresponding `--diff-merges=<format>` option of linkgit:git-log[1],
87+
and include them in the comparison.
88+
+
89+
Note: In the common case, the `remerge` mode will be the most natural one
90+
to use, as it shows only the diff on top of what Git's merge machinery would
91+
have produced. In other words, if a merge commit is the result of a
92+
non-conflicting `git merge`, the `remerge` mode will represent it with an empty
93+
diff.
94+
8495
--[no-]notes[=<ref>]::
8596
This flag is passed to the `git log` program
8697
(see linkgit:git-log[1]) that generates the patches.

builtin/range-diff.c

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ int cmd_range_diff(int argc, const char **argv, const char *prefix)
1616
{
1717
struct diff_options diffopt = { NULL };
1818
struct strvec other_arg = STRVEC_INIT;
19+
struct strvec diff_merges_arg = STRVEC_INIT;
1920
struct range_diff_options range_diff_opts = {
2021
.creation_factor = RANGE_DIFF_CREATION_FACTOR_DEFAULT,
2122
.diffopt = &diffopt,
@@ -31,6 +32,8 @@ int cmd_range_diff(int argc, const char **argv, const char *prefix)
3132
OPT_PASSTHRU_ARGV(0, "notes", &other_arg,
3233
N_("notes"), N_("passed to 'git log'"),
3334
PARSE_OPT_OPTARG),
35+
OPT_PASSTHRU_ARGV(0, "diff-merges", &diff_merges_arg,
36+
N_("style"), N_("passed to 'git log'"), 0),
3437
OPT_BOOL(0, "left-only", &left_only,
3538
N_("only emit output related to the first range")),
3639
OPT_BOOL(0, "right-only", &right_only,
@@ -57,6 +60,12 @@ int cmd_range_diff(int argc, const char **argv, const char *prefix)
5760
if (!simple_color)
5861
diffopt.use_color = 1;
5962

63+
/* If `--diff-merges` was specified, imply `--merges` */
64+
if (diff_merges_arg.nr) {
65+
range_diff_opts.include_merges = 1;
66+
strvec_pushv(&other_arg, diff_merges_arg.v);
67+
}
68+
6069
for (i = 0; i < argc; i++)
6170
if (!strcmp(argv[i], "--")) {
6271
dash_dash = i;
@@ -150,6 +159,7 @@ int cmd_range_diff(int argc, const char **argv, const char *prefix)
150159
res = show_range_diff(range1.buf, range2.buf, &range_diff_opts);
151160

152161
strvec_clear(&other_arg);
162+
strvec_clear(&diff_merges_arg);
153163
strbuf_release(&range1);
154164
strbuf_release(&range2);
155165

range-diff.c

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,8 @@ struct patch_util {
3131
* as struct object_id (will need to be free()d).
3232
*/
3333
static int read_patches(const char *range, struct string_list *list,
34-
const struct strvec *other_arg)
34+
const struct strvec *other_arg,
35+
unsigned int include_merges)
3536
{
3637
struct child_process cp = CHILD_PROCESS_INIT;
3738
struct strbuf buf = STRBUF_INIT, contents = STRBUF_INIT;
@@ -42,7 +43,7 @@ static int read_patches(const char *range, struct string_list *list,
4243
size_t size;
4344
int ret = -1;
4445

45-
strvec_pushl(&cp.args, "log", "--no-color", "-p", "--no-merges",
46+
strvec_pushl(&cp.args, "log", "--no-color", "-p",
4647
"--reverse", "--date-order", "--decorate=no",
4748
"--no-prefix", "--submodule=short",
4849
/*
@@ -57,6 +58,8 @@ static int read_patches(const char *range, struct string_list *list,
5758
"--pretty=medium",
5859
"--notes",
5960
NULL);
61+
if (!include_merges)
62+
strvec_push(&cp.args, "--no-merges");
6063
strvec_push(&cp.args, range);
6164
if (other_arg)
6265
strvec_pushv(&cp.args, other_arg->v);
@@ -89,12 +92,15 @@ static int read_patches(const char *range, struct string_list *list,
8992
}
9093

9194
if (skip_prefix(line, "commit ", &p)) {
95+
char *q;
9296
if (util) {
9397
string_list_append(list, buf.buf)->util = util;
9498
strbuf_reset(&buf);
9599
}
96100
CALLOC_ARRAY(util, 1);
97-
if (get_oid(p, &util->oid)) {
101+
if (include_merges && (q = strstr(p, " (from ")))
102+
*q = '\0';
103+
if (repo_get_oid(the_repository, p, &util->oid)) {
98104
error(_("could not parse commit '%s'"), p);
99105
FREE_AND_NULL(util);
100106
string_list_clear(list, 1);
@@ -559,13 +565,14 @@ int show_range_diff(const char *range1, const char *range2,
559565

560566
struct string_list branch1 = STRING_LIST_INIT_DUP;
561567
struct string_list branch2 = STRING_LIST_INIT_DUP;
568+
unsigned int include_merges = range_diff_opts->include_merges;
562569

563570
if (range_diff_opts->left_only && range_diff_opts->right_only)
564571
res = error(_("options '%s' and '%s' cannot be used together"), "--left-only", "--right-only");
565572

566-
if (!res && read_patches(range1, &branch1, range_diff_opts->other_arg))
573+
if (!res && read_patches(range1, &branch1, range_diff_opts->other_arg, include_merges))
567574
res = error(_("could not parse log for '%s'"), range1);
568-
if (!res && read_patches(range2, &branch2, range_diff_opts->other_arg))
575+
if (!res && read_patches(range2, &branch2, range_diff_opts->other_arg, include_merges))
569576
res = error(_("could not parse log for '%s'"), range2);
570577

571578
if (!res) {

range-diff.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ struct range_diff_options {
1010
int creation_factor;
1111
unsigned dual_color:1;
1212
unsigned left_only:1, right_only:1;
13+
unsigned include_merges:1;
1314
const struct diff_options *diffopt; /* may be NULL */
1415
const struct strvec *other_arg; /* may be NULL */
1516
};

t/t3206-range-diff.sh

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -866,4 +866,20 @@ test_expect_success 'submodule changes are shown irrespective of diff.submodule'
866866
test_cmp expect actual
867867
'
868868

869+
test_expect_success '--diff-merges' '
870+
renamed_oid=$(git rev-parse --short renamed-file) &&
871+
tree=$(git merge-tree unmodified renamed-file) &&
872+
clean=$(git commit-tree -m merge -p unmodified -p renamed-file $tree) &&
873+
clean_oid=$(git rev-parse --short $clean) &&
874+
conflict=$(git commit-tree -m merge -p unmodified -p renamed-file^ $tree) &&
875+
conflict_oid=$(git rev-parse --short $conflict) &&
876+
877+
git range-diff --diff-merges=1 $clean...$conflict >actual &&
878+
cat >expect <<-EOF &&
879+
1: $renamed_oid < -: ------- s/12/B/
880+
2: $clean_oid = 1: $conflict_oid merge
881+
EOF
882+
test_cmp expect actual
883+
'
884+
869885
test_done

0 commit comments

Comments
 (0)