Skip to content

Commit 3d09c22

Browse files
Denton-Lgitster
authored andcommitted
builtin/diff-tree: learn --merge-base
The previous commit introduced ---merge-base a way to take the diff between the working tree or index and the merge base between an arbitrary commit and HEAD. It makes sense to extend this option to support the case where two commits are given too and behave in a manner identical to `git diff A...B`. Introduce the --merge-base flag as an alternative to triple-dot notation. Thus, we would be able to write the above as `git diff --merge-base A B`. Signed-off-by: Denton Liu <[email protected]> Signed-off-by: Junio C Hamano <[email protected]>
1 parent 0f5a1d4 commit 3d09c22

File tree

5 files changed

+89
-16
lines changed

5 files changed

+89
-16
lines changed

Documentation/git-diff-tree.txt

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ SYNOPSIS
1010
--------
1111
[verse]
1212
'git diff-tree' [--stdin] [-m] [-s] [-v] [--no-commit-id] [--pretty]
13-
[-t] [-r] [-c | --cc] [--combined-all-paths] [--root]
13+
[-t] [-r] [-c | --cc] [--combined-all-paths] [--root] [--merge-base]
1414
[<common diff options>] <tree-ish> [<tree-ish>] [<path>...]
1515

1616
DESCRIPTION
@@ -43,6 +43,11 @@ include::diff-options.txt[]
4343
When `--root` is specified the initial commit will be shown as a big
4444
creation event. This is equivalent to a diff against the NULL tree.
4545

46+
--merge-base::
47+
Instead of comparing the <tree-ish>s directly, use the merge
48+
base between the two <tree-ish>s as the "before" side. There
49+
must be two <tree-ish>s given and they must both be commits.
50+
4651
--stdin::
4752
When `--stdin` is specified, the command does not take
4853
<tree-ish> arguments from the command line. Instead, it

Documentation/git-diff.txt

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ SYNOPSIS
1111
[verse]
1212
'git diff' [<options>] [<commit>] [--] [<path>...]
1313
'git diff' [<options>] --cached [--merge-base] [<commit>] [--] [<path>...]
14-
'git diff' [<options>] <commit> [<commit>...] <commit> [--] [<path>...]
14+
'git diff' [<options>] [--merge-base] <commit> [<commit>...] <commit> [--] [<path>...]
1515
'git diff' [<options>] <commit>...<commit> [--] [<path>...]
1616
'git diff' [<options>] <blob> <blob>
1717
'git diff' [<options>] --no-index [--] <path> <path>
@@ -62,10 +62,14 @@ of <commit> and HEAD. `git diff --merge-base A` is equivalent to
6262
branch name to compare with the tip of a different
6363
branch.
6464

65-
'git diff' [<options>] <commit> <commit> [--] [<path>...]::
65+
'git diff' [<options>] [--merge-base] <commit> <commit> [--] [<path>...]::
6666

6767
This is to view the changes between two arbitrary
6868
<commit>.
69+
+
70+
If --merge-base is given, use the merge base of the two commits for the
71+
"before" side. `git diff --merge-base A B` is equivalent to
72+
`git diff $(git merge-base A B) B`.
6973

7074
'git diff' [<options>] <commit> <commit>... <commit> [--] [<path>...]::
7175

builtin/diff-tree.c

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,7 @@ int cmd_diff_tree(int argc, const char **argv, const char *prefix)
111111
struct setup_revision_opt s_r_opt;
112112
struct userformat_want w;
113113
int read_stdin = 0;
114+
int merge_base = 0;
114115

115116
if (argc == 2 && !strcmp(argv[1], "-h"))
116117
usage(diff_tree_usage);
@@ -143,9 +144,18 @@ int cmd_diff_tree(int argc, const char **argv, const char *prefix)
143144
read_stdin = 1;
144145
continue;
145146
}
147+
if (!strcmp(arg, "--merge-base")) {
148+
merge_base = 1;
149+
continue;
150+
}
146151
usage(diff_tree_usage);
147152
}
148153

154+
if (read_stdin && merge_base)
155+
die(_("--stdin and --merge-base are mutually exclusive"));
156+
if (merge_base && opt->pending.nr != 2)
157+
die(_("--merge-base only works with two commits"));
158+
149159
/*
150160
* NOTE! We expect "a..b" to expand to "^a b" but it is
151161
* perfectly valid for revision range parser to yield "b ^a",
@@ -165,7 +175,12 @@ int cmd_diff_tree(int argc, const char **argv, const char *prefix)
165175
case 2:
166176
tree1 = opt->pending.objects[0].item;
167177
tree2 = opt->pending.objects[1].item;
168-
if (tree2->flags & UNINTERESTING) {
178+
if (merge_base) {
179+
struct object_id oid;
180+
181+
diff_get_merge_base(opt, &oid);
182+
tree1 = lookup_object(the_repository, &oid);
183+
} else if (tree2->flags & UNINTERESTING) {
169184
SWAP(tree2, tree1);
170185
}
171186
diff_tree_oid(&tree1->oid, &tree2->oid, "", &opt->diffopt);

builtin/diff.c

Lines changed: 27 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@
2626
static const char builtin_diff_usage[] =
2727
"git diff [<options>] [<commit>] [--] [<path>...]\n"
2828
" or: git diff [<options>] --cached [<commit>] [--] [<path>...]\n"
29-
" or: git diff [<options>] <commit> [<commit>...] <commit> [--] [<path>...]\n"
29+
" or: git diff [<options>] <commit> [--merge-base] [<commit>...] <commit> [--] [<path>...]\n"
3030
" or: git diff [<options>] <commit>...<commit>] [--] [<path>...]\n"
3131
" or: git diff [<options>] <blob> <blob>]\n"
3232
" or: git diff [<options>] --no-index [--] <path> <path>]\n"
@@ -172,19 +172,34 @@ static int builtin_diff_tree(struct rev_info *revs,
172172
struct object_array_entry *ent1)
173173
{
174174
const struct object_id *(oid[2]);
175-
int swap = 0;
175+
struct object_id mb_oid;
176+
int merge_base = 0;
176177

177-
if (argc > 1)
178-
usage(builtin_diff_usage);
178+
while (1 < argc) {
179+
const char *arg = argv[1];
180+
if (!strcmp(arg, "--merge-base"))
181+
merge_base = 1;
182+
else
183+
usage(builtin_diff_usage);
184+
argv++; argc--;
185+
}
179186

180-
/*
181-
* We saw two trees, ent0 and ent1. If ent1 is uninteresting,
182-
* swap them.
183-
*/
184-
if (ent1->item->flags & UNINTERESTING)
185-
swap = 1;
186-
oid[swap] = &ent0->item->oid;
187-
oid[1 - swap] = &ent1->item->oid;
187+
if (merge_base) {
188+
diff_get_merge_base(revs, &mb_oid);
189+
oid[0] = &mb_oid;
190+
oid[1] = &revs->pending.objects[1].item->oid;
191+
} else {
192+
int swap = 0;
193+
194+
/*
195+
* We saw two trees, ent0 and ent1. If ent1 is uninteresting,
196+
* swap them.
197+
*/
198+
if (ent1->item->flags & UNINTERESTING)
199+
swap = 1;
200+
oid[swap] = &ent0->item->oid;
201+
oid[1 - swap] = &ent1->item->oid;
202+
}
188203
diff_tree_oid(oid[0], oid[1], "", &revs->diffopt);
189204
log_tree_diff_flush(revs);
190205
return 0;

t/t4068-diff-symmetric-merge-base.sh

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -156,4 +156,38 @@ do
156156
'
157157
done
158158

159+
for cmd in diff-tree diff
160+
do
161+
test_expect_success "$cmd --merge-base with two commits" '
162+
git $cmd commit-C master >expect &&
163+
git $cmd --merge-base br2 master >actual &&
164+
test_cmp expect actual
165+
'
166+
167+
test_expect_success "$cmd --merge-base commit and non-commit" '
168+
test_must_fail git $cmd --merge-base br2 master^{tree} 2>err &&
169+
test_i18ngrep "fatal: --merge-base only works with commits" err
170+
'
171+
172+
test_expect_success "$cmd --merge-base with no merge bases and two commits" '
173+
test_must_fail git $cmd --merge-base br2 br3 2>err &&
174+
test_i18ngrep "fatal: no merge base found" err
175+
'
176+
177+
test_expect_success "$cmd --merge-base with multiple merge bases and two commits" '
178+
test_must_fail git $cmd --merge-base master br1 2>err &&
179+
test_i18ngrep "fatal: multiple merge bases found" err
180+
'
181+
done
182+
183+
test_expect_success 'diff-tree --merge-base with one commit' '
184+
test_must_fail git diff-tree --merge-base master 2>err &&
185+
test_i18ngrep "fatal: --merge-base only works with two commits" err
186+
'
187+
188+
test_expect_success 'diff --merge-base with range' '
189+
test_must_fail git diff --merge-base br2..br3 2>err &&
190+
test_i18ngrep "fatal: --merge-base does not work with ranges" err
191+
'
192+
159193
test_done

0 commit comments

Comments
 (0)