]> git.ipfire.org Git - thirdparty/git.git/commitdiff
builtin/diff-tree: learn --merge-base
authorDenton Liu <liu.denton@gmail.com>
Mon, 14 Sep 2020 18:36:52 +0000 (11:36 -0700)
committerJunio C Hamano <gitster@pobox.com>
Mon, 21 Sep 2020 20:37:03 +0000 (13:37 -0700)
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>
Documentation/git-diff-tree.txt
Documentation/git-diff.txt
builtin/diff-tree.c
builtin/diff.c
t/t4068-diff-symmetric-merge-base.sh

index 5c8a2a5e9755db17be4063cdcb2edb8b48237436..2fc24c542f8cbd3e20af3575b09f52b6a4de5822 100644 (file)
@@ -10,7 +10,7 @@ SYNOPSIS
 --------
 [verse]
 'git diff-tree' [--stdin] [-m] [-s] [-v] [--no-commit-id] [--pretty]
-             [-t] [-r] [-c | --cc] [--combined-all-paths] [--root]
+             [-t] [-r] [-c | --cc] [--combined-all-paths] [--root] [--merge-base]
              [<common diff options>] <tree-ish> [<tree-ish>] [<path>...]
 
 DESCRIPTION
@@ -43,6 +43,11 @@ include::diff-options.txt[]
        When `--root` is specified the initial commit will be shown as a big
        creation event. This is equivalent to a diff against the NULL tree.
 
+--merge-base::
+       Instead of comparing the <tree-ish>s directly, use the merge
+       base between the two <tree-ish>s as the "before" side.  There
+       must be two <tree-ish>s given and they must both be commits.
+
 --stdin::
        When `--stdin` is specified, the command does not take
        <tree-ish> arguments from the command line.  Instead, it
index 762ee6d074016425590a090de238e0bf6e7813ad..7f4c8a8ce7fd54f87e2c3936a6a1cf1cc623c60d 100644 (file)
@@ -11,7 +11,7 @@ SYNOPSIS
 [verse]
 'git diff' [<options>] [<commit>] [--] [<path>...]
 'git diff' [<options>] --cached [--merge-base] [<commit>] [--] [<path>...]
-'git diff' [<options>] <commit> [<commit>...] <commit> [--] [<path>...]
+'git diff' [<options>] [--merge-base] <commit> [<commit>...] <commit> [--] [<path>...]
 'git diff' [<options>] <commit>...<commit> [--] [<path>...]
 'git diff' [<options>] <blob> <blob>
 'git diff' [<options>] --no-index [--] <path> <path>
@@ -62,10 +62,14 @@ of <commit> and HEAD.  `git diff --merge-base A` is equivalent to
        branch name to compare with the tip of a different
        branch.
 
-'git diff' [<options>] <commit> <commit> [--] [<path>...]::
+'git diff' [<options>] [--merge-base] <commit> <commit> [--] [<path>...]::
 
        This is to view the changes between two arbitrary
        <commit>.
++
+If --merge-base is given, use the merge base of the two commits for the
+"before" side.  `git diff --merge-base A B` is equivalent to
+`git diff $(git merge-base A B) B`.
 
 'git diff' [<options>] <commit> <commit>... <commit> [--] [<path>...]::
 
index 802363d0a2295f855828121a5b99184d6475b74f..9fc95e959f0e0dba69cc1f96597bb77f30997d1e 100644 (file)
@@ -111,6 +111,7 @@ int cmd_diff_tree(int argc, const char **argv, const char *prefix)
        struct setup_revision_opt s_r_opt;
        struct userformat_want w;
        int read_stdin = 0;
+       int merge_base = 0;
 
        if (argc == 2 && !strcmp(argv[1], "-h"))
                usage(diff_tree_usage);
@@ -143,9 +144,18 @@ int cmd_diff_tree(int argc, const char **argv, const char *prefix)
                        read_stdin = 1;
                        continue;
                }
+               if (!strcmp(arg, "--merge-base")) {
+                       merge_base = 1;
+                       continue;
+               }
                usage(diff_tree_usage);
        }
 
+       if (read_stdin && merge_base)
+               die(_("--stdin and --merge-base are mutually exclusive"));
+       if (merge_base && opt->pending.nr != 2)
+               die(_("--merge-base only works with two commits"));
+
        /*
         * NOTE!  We expect "a..b" to expand to "^a b" but it is
         * 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)
        case 2:
                tree1 = opt->pending.objects[0].item;
                tree2 = opt->pending.objects[1].item;
-               if (tree2->flags & UNINTERESTING) {
+               if (merge_base) {
+                       struct object_id oid;
+
+                       diff_get_merge_base(opt, &oid);
+                       tree1 = lookup_object(the_repository, &oid);
+               } else if (tree2->flags & UNINTERESTING) {
                        SWAP(tree2, tree1);
                }
                diff_tree_oid(&tree1->oid, &tree2->oid, "", &opt->diffopt);
index 1baea18ae04dcf4ad420ff8c75fb854d5137753e..b50fc68c2a7ab4431c56d44f477875a2e3606867 100644 (file)
@@ -26,7 +26,7 @@
 static const char builtin_diff_usage[] =
 "git diff [<options>] [<commit>] [--] [<path>...]\n"
 "   or: git diff [<options>] --cached [<commit>] [--] [<path>...]\n"
-"   or: git diff [<options>] <commit> [<commit>...] <commit> [--] [<path>...]\n"
+"   or: git diff [<options>] <commit> [--merge-base] [<commit>...] <commit> [--] [<path>...]\n"
 "   or: git diff [<options>] <commit>...<commit>] [--] [<path>...]\n"
 "   or: git diff [<options>] <blob> <blob>]\n"
 "   or: git diff [<options>] --no-index [--] <path> <path>]\n"
@@ -172,19 +172,34 @@ static int builtin_diff_tree(struct rev_info *revs,
                             struct object_array_entry *ent1)
 {
        const struct object_id *(oid[2]);
-       int swap = 0;
+       struct object_id mb_oid;
+       int merge_base = 0;
 
-       if (argc > 1)
-               usage(builtin_diff_usage);
+       while (1 < argc) {
+               const char *arg = argv[1];
+               if (!strcmp(arg, "--merge-base"))
+                       merge_base = 1;
+               else
+                       usage(builtin_diff_usage);
+               argv++; argc--;
+       }
 
-       /*
-        * We saw two trees, ent0 and ent1.  If ent1 is uninteresting,
-        * swap them.
-        */
-       if (ent1->item->flags & UNINTERESTING)
-               swap = 1;
-       oid[swap] = &ent0->item->oid;
-       oid[1 - swap] = &ent1->item->oid;
+       if (merge_base) {
+               diff_get_merge_base(revs, &mb_oid);
+               oid[0] = &mb_oid;
+               oid[1] = &revs->pending.objects[1].item->oid;
+       } else {
+               int swap = 0;
+
+               /*
+                * We saw two trees, ent0 and ent1.  If ent1 is uninteresting,
+                * swap them.
+                */
+               if (ent1->item->flags & UNINTERESTING)
+                       swap = 1;
+               oid[swap] = &ent0->item->oid;
+               oid[1 - swap] = &ent1->item->oid;
+       }
        diff_tree_oid(oid[0], oid[1], "", &revs->diffopt);
        log_tree_diff_flush(revs);
        return 0;
index 49432379cb2c1e25755bb3c316d267e22ca744e4..03487cc945d0ffcf2e16a2a1e0acf3989703905c 100755 (executable)
@@ -156,4 +156,38 @@ do
        '
 done
 
+for cmd in diff-tree diff
+do
+       test_expect_success "$cmd --merge-base with two commits" '
+               git $cmd commit-C master >expect &&
+               git $cmd --merge-base br2 master >actual &&
+               test_cmp expect actual
+       '
+
+       test_expect_success "$cmd --merge-base commit and non-commit" '
+               test_must_fail git $cmd --merge-base br2 master^{tree} 2>err &&
+               test_i18ngrep "fatal: --merge-base only works with commits" err
+       '
+
+       test_expect_success "$cmd --merge-base with no merge bases and two commits" '
+               test_must_fail git $cmd --merge-base br2 br3 2>err &&
+               test_i18ngrep "fatal: no merge base found" err
+       '
+
+       test_expect_success "$cmd --merge-base with multiple merge bases and two commits" '
+               test_must_fail git $cmd --merge-base master br1 2>err &&
+               test_i18ngrep "fatal: multiple merge bases found" err
+       '
+done
+
+test_expect_success 'diff-tree --merge-base with one commit' '
+       test_must_fail git diff-tree --merge-base master 2>err &&
+       test_i18ngrep "fatal: --merge-base only works with two commits" err
+'
+
+test_expect_success 'diff --merge-base with range' '
+       test_must_fail git diff --merge-base br2..br3 2>err &&
+       test_i18ngrep "fatal: --merge-base does not work with ranges" err
+'
+
 test_done