Skip to content

Commit a79c6b6

Browse files
rscharfegitster
authored andcommitted
diff: support ^! for merges
revision.c::handle_revision_arg_1() resolves <rev>^! by first adding the negated parents and then <rev> itself. builtin_diff_combined() expects the first tree to be the merge and the remaining ones to be the parents, though. This mismatch results in bogus diff output. Remember the first tree that doesn't belong to a parent and use it instead of blindly picking the first one. This makes "git diff <rev>^!" consistent with "git show <rev>^!". Reported-by: Tim Jaacks <[email protected]> Suggested-by: Junio C Hamano <[email protected]> Signed-off-by: René Scharfe <[email protected]> Signed-off-by: Junio C Hamano <[email protected]>
1 parent 9f91da7 commit a79c6b6

File tree

3 files changed

+32
-9
lines changed

3 files changed

+32
-9
lines changed

Documentation/git-diff.txt

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -79,10 +79,10 @@ If --merge-base is given, use the merge base of the two commits for the
7979

8080
This form is to view the results of a merge commit. The first
8181
listed <commit> must be the merge itself; the remaining two or
82-
more commits should be its parents. A convenient way to produce
83-
the desired set of revisions is to use the `^@` suffix.
84-
For instance, if `master` names a merge commit, `git diff master
85-
master^@` gives the same combined diff as `git show master`.
82+
more commits should be its parents. Convenient ways to produce
83+
the desired set of revisions are to use the suffixes `^@` and
84+
`^!`. If A is a merge commit, then `git diff A A^@`,
85+
`git diff A^!` and `git show A` all give the same combined diff.
8686

8787
'git diff' [<options>] <commit>..<commit> [--] [<path>...]::
8888

builtin/diff.c

Lines changed: 18 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -209,19 +209,26 @@ static int builtin_diff_tree(struct rev_info *revs,
209209
static int builtin_diff_combined(struct rev_info *revs,
210210
int argc, const char **argv,
211211
struct object_array_entry *ent,
212-
int ents)
212+
int ents, int first_non_parent)
213213
{
214214
struct oid_array parents = OID_ARRAY_INIT;
215215
int i;
216216

217217
if (argc > 1)
218218
usage(builtin_diff_usage);
219219

220+
if (first_non_parent < 0)
221+
die(_("no merge given, only parents."));
222+
if (first_non_parent >= ents)
223+
BUG("first_non_parent out of range: %d", first_non_parent);
224+
220225
diff_merges_set_dense_combined_if_unset(revs);
221226

222-
for (i = 1; i < ents; i++)
223-
oid_array_append(&parents, &ent[i].item->oid);
224-
diff_tree_combined(&ent[0].item->oid, &parents, revs);
227+
for (i = 0; i < ents; i++) {
228+
if (i != first_non_parent)
229+
oid_array_append(&parents, &ent[i].item->oid);
230+
}
231+
diff_tree_combined(&ent[first_non_parent].item->oid, &parents, revs);
225232
oid_array_clear(&parents);
226233
return 0;
227234
}
@@ -385,6 +392,7 @@ int cmd_diff(int argc, const char **argv, const char *prefix)
385392
int i;
386393
struct rev_info rev;
387394
struct object_array ent = OBJECT_ARRAY_INIT;
395+
int first_non_parent = -1;
388396
int blobs = 0, paths = 0;
389397
struct object_array_entry *blob[2];
390398
int nongit = 0, no_index = 0;
@@ -543,6 +551,10 @@ int cmd_diff(int argc, const char **argv, const char *prefix)
543551
continue;
544552
obj->flags |= flags;
545553
add_object_array(obj, name, &ent);
554+
if (first_non_parent < 0 &&
555+
(i >= rev.cmdline.nr || /* HEAD by hand. */
556+
rev.cmdline.rev[i].whence != REV_CMD_PARENTS_ONLY))
557+
first_non_parent = ent.nr - 1;
546558
} else if (obj->type == OBJ_BLOB) {
547559
if (2 <= blobs)
548560
die(_("more than two blobs given: '%s'"), name);
@@ -590,7 +602,8 @@ int cmd_diff(int argc, const char **argv, const char *prefix)
590602
&ent.objects[0], &ent.objects[1]);
591603
} else
592604
result = builtin_diff_combined(&rev, argc, argv,
593-
ent.objects, ent.nr);
605+
ent.objects, ent.nr,
606+
first_non_parent);
594607
result = diff_result_code(&rev.diffopt, result);
595608
if (1 < rev.diffopt.skip_stat_unmatch)
596609
refresh_index_quietly();

t/t4038-diff-combined.sh

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,11 +80,21 @@ test_expect_success 'check combined output (1)' '
8080
verify_helper sidewithone
8181
'
8282

83+
test_expect_success 'check combined output (1) with git diff <rev>^!' '
84+
git diff sidewithone^! -- >sidewithone &&
85+
verify_helper sidewithone
86+
'
87+
8388
test_expect_success 'check combined output (2)' '
8489
git show sidesansone -- >sidesansone &&
8590
verify_helper sidesansone
8691
'
8792

93+
test_expect_success 'check combined output (2) with git diff <rev>^!' '
94+
git diff sidesansone^! -- >sidesansone &&
95+
verify_helper sidesansone
96+
'
97+
8898
test_expect_success 'diagnose truncated file' '
8999
>file &&
90100
git add file &&

0 commit comments

Comments
 (0)