@matheusbsbs / matheus-bagliano / commits / 3d09c22869
Fork 0

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 <liu.denton@gmail.com> Signed-off-by: Junio C Hamano <gitster@pobox.com>

Denton Liu committed Sep 14, 2020 at 11:36 UTC 3d09c22869a7bd47f1683031937af0ed93b2fa3b
5 files changed +89 -16
Documentation/git-diff-tree.txt
+6 -1
@@ -10,7 +10,7 @@ SYNOPSIS
10 --------
11 [verse]
12 '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]
14 [<common diff options>] <tree-ish> [<tree-ish>] [<path>...]
15
16 DESCRIPTION
@@ -43,6 +43,11 @@ include::diff-options.txt[]
43 When `--root` is specified the initial commit will be shown as a big
44 creation event. This is equivalent to a diff against the NULL tree.
45
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 +
51 --stdin::
52 When `--stdin` is specified, the command does not take
53 <tree-ish> arguments from the command line. Instead, it
Documentation/git-diff.txt
+6 -2
@@ -11,7 +11,7 @@ SYNOPSIS
11 [verse]
12 'git diff' [<options>] [<commit>] [--] [<path>...]
13 '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>...]
15 'git diff' [<options>] <commit>...<commit> [--] [<path>...]
16 'git diff' [<options>] <blob> <blob>
17 'git diff' [<options>] --no-index [--] <path> <path>
@@ -62,10 +62,14 @@ of <commit> and HEAD. `git diff --merge-base A` is equivalent to
62 branch name to compare with the tip of a different
63 branch.
64
65 -'git diff' [<options>] <commit> <commit> [--] [<path>...]::
65 +'git diff' [<options>] [--merge-base] <commit> <commit> [--] [<path>...]::
66
67 This is to view the changes between two arbitrary
68 <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`.
73
74 'git diff' [<options>] <commit> <commit>... <commit> [--] [<path>...]::
75
builtin/diff-tree.c
+16 -1
@@ -111,6 +111,7 @@ int cmd_diff_tree(int argc, const char **argv, const char *prefix)
111 struct setup_revision_opt s_r_opt;
112 struct userformat_want w;
113 int read_stdin = 0;
114 + int merge_base = 0;
115
116 if (argc == 2 && !strcmp(argv[1], "-h"))
117 usage(diff_tree_usage);
@@ -143,9 +144,18 @@ int cmd_diff_tree(int argc, const char **argv, const char *prefix)
144 read_stdin = 1;
145 continue;
146 }
147 + if (!strcmp(arg, "--merge-base")) {
148 + merge_base = 1;
149 + continue;
150 + }
151 usage(diff_tree_usage);
152 }
153
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 +
159 /*
160 * NOTE! We expect "a..b" to expand to "^a b" but it is
161 * 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)
175 case 2:
176 tree1 = opt->pending.objects[0].item;
177 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) {
184 SWAP(tree2, tree1);
185 }
186 diff_tree_oid(&tree1->oid, &tree2->oid, "", &opt->diffopt);
builtin/diff.c
+27 -12
@@ -26,7 +26,7 @@
26 static const char builtin_diff_usage[] =
27 "git diff [<options>] [<commit>] [--] [<path>...]\n"
28 " 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"
30 " or: git diff [<options>] <commit>...<commit>] [--] [<path>...]\n"
31 " or: git diff [<options>] <blob> <blob>]\n"
32 " or: git diff [<options>] --no-index [--] <path> <path>]\n"
@@ -172,19 +172,34 @@ static int builtin_diff_tree(struct rev_info *revs,
172 struct object_array_entry *ent1)
173 {
174 const struct object_id *(oid[2]);
175 - int swap = 0;
175 + struct object_id mb_oid;
176 + int merge_base = 0;
177
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 + }
186
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 + }
203 diff_tree_oid(oid[0], oid[1], "", &revs->diffopt);
204 log_tree_diff_flush(revs);
205 return 0;
t/t4068-diff-symmetric-merge-base.sh
+34
@@ -156,4 +156,38 @@ do
156 '
157 done
158
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 +
193 test_done