]> git.ipfire.org Git - thirdparty/git.git/commitdiff
builtin/diff-index: learn --merge-base
authorDenton Liu <liu.denton@gmail.com>
Sun, 20 Sep 2020 11:22:25 +0000 (04:22 -0700)
committerJunio C Hamano <gitster@pobox.com>
Mon, 21 Sep 2020 04:30:26 +0000 (21:30 -0700)
There is currently no easy way to take the diff between the working tree
or index and the merge base between an arbitrary commit and HEAD. Even
diff's `...` notation doesn't allow this because it only works between
commits. However, the ability to do this would be desirable to a user
who would like to see all the changes they've made on a branch plus
uncommitted changes without taking into account changes made in the
upstream branch.

Teach diff-index and diff (with one commit) the --merge-base option
which allows a user to use the merge base of a commit and HEAD as the
"before" side.

Signed-off-by: Denton Liu <liu.denton@gmail.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
Documentation/git-diff-index.txt
Documentation/git-diff.txt
builtin/diff-index.c
builtin/diff.c
diff-lib.c
diff.h
t/t4068-diff-symmetric-merge-base.sh

index 25fe165f0014b087e8b87177dd8573b29fa75782..27acb31cbf26f6d842a7445324950f6dfc2b2e68 100644 (file)
@@ -9,7 +9,7 @@ git-diff-index - Compare a tree to the working tree or index
 SYNOPSIS
 --------
 [verse]
-'git diff-index' [-m] [--cached] [<common diff options>] <tree-ish> [<path>...]
+'git diff-index' [-m] [--cached] [--merge-base] [<common diff options>] <tree-ish> [<path>...]
 
 DESCRIPTION
 -----------
@@ -29,6 +29,11 @@ include::diff-options.txt[]
 --cached::
        Do not consider the on-disk file at all.
 
+--merge-base::
+       Instead of comparing <tree-ish> directly, use the merge base
+       between <tree-ish> and HEAD instead.  <tree-ish> must be a
+       commit.
+
 -m::
        By default, files recorded in the index but not checked
        out are reported as deleted.  This flag makes
index 8f7b4ed3caacebfc7c462dd2f6af2295e8e7cc59..762ee6d074016425590a090de238e0bf6e7813ad 100644 (file)
@@ -10,7 +10,7 @@ SYNOPSIS
 --------
 [verse]
 'git diff' [<options>] [<commit>] [--] [<path>...]
-'git diff' [<options>] --cached [<commit>] [--] [<path>...]
+'git diff' [<options>] --cached [--merge-base] [<commit>] [--] [<path>...]
 'git diff' [<options>] <commit> [<commit>...] <commit> [--] [<path>...]
 'git diff' [<options>] <commit>...<commit> [--] [<path>...]
 'git diff' [<options>] <blob> <blob>
@@ -40,7 +40,7 @@ files on disk.
        or when running the command outside a working tree
        controlled by Git. This form implies `--exit-code`.
 
-'git diff' [<options>] --cached [<commit>] [--] [<path>...]::
+'git diff' [<options>] --cached [--merge-base] [<commit>] [--] [<path>...]::
 
        This form is to view the changes you staged for the next
        commit relative to the named <commit>.  Typically you
@@ -49,6 +49,10 @@ files on disk.
        If HEAD does not exist (e.g. unborn branches) and
        <commit> is not given, it shows all staged changes.
        --staged is a synonym of --cached.
++
+If --merge-base is given, instead of using <commit>, use the merge base
+of <commit> and HEAD.  `git diff --merge-base A` is equivalent to
+`git diff $(git merge-base A HEAD)`.
 
 'git diff' [<options>] <commit> [--] [<path>...]::
 
@@ -89,8 +93,8 @@ files on disk.
 
 Just in case you are doing something exotic, it should be
 noted that all of the <commit> in the above description, except
-in the last two forms that use `..` notations, can be any
-<tree>.
+in the `--merge-base` case and in the last two forms that use `..`
+notations, can be any <tree>.
 
 For a more complete list of ways to spell <commit>, see
 "SPECIFYING REVISIONS" section in linkgit:gitrevisions[7].
index c3878f7ad641823b6a27c2df76f8e317c2fc1478..7f5281c46168ed72505fe9ee3cfa200c200b4cb1 100644 (file)
@@ -33,6 +33,8 @@ int cmd_diff_index(int argc, const char **argv, const char *prefix)
 
                if (!strcmp(arg, "--cached"))
                        option |= DIFF_INDEX_CACHED;
+               else if (!strcmp(arg, "--merge-base"))
+                       option |= DIFF_INDEX_MERGE_BASE;
                else
                        usage(diff_cache_usage);
        }
index e45e19e37e19f7c15e387fa5f76cfc2263b230ab..1baea18ae04dcf4ad420ff8c75fb854d5137753e 100644 (file)
@@ -139,6 +139,8 @@ static int builtin_diff_index(struct rev_info *revs,
                const char *arg = argv[1];
                if (!strcmp(arg, "--cached") || !strcmp(arg, "--staged"))
                        option |= DIFF_INDEX_CACHED;
+               else if (!strcmp(arg, "--merge-base"))
+                       option |= DIFF_INDEX_MERGE_BASE;
                else
                        usage(builtin_diff_usage);
                argv++; argc--;
index 468e3fe854aa605b5826b8b4a73a40ecb467d1c9..c578560997c4c2e6d4f88f4c97f0ef9fa465c315 100644 (file)
@@ -561,13 +561,26 @@ int run_diff_index(struct rev_info *revs, unsigned int option)
 {
        struct object_array_entry *ent;
        int cached = !!(option & DIFF_INDEX_CACHED);
+       int merge_base = !!(option & DIFF_INDEX_MERGE_BASE);
+       struct object_id oid;
+       const char *name;
+       char merge_base_hex[GIT_MAX_HEXSZ + 1];
 
        if (revs->pending.nr != 1)
                BUG("run_diff_index must be passed exactly one tree");
 
        trace_performance_enter();
        ent = revs->pending.objects;
-       if (diff_cache(revs, &ent->item->oid, ent->name, cached))
+
+       if (merge_base) {
+               diff_get_merge_base(revs, &oid);
+               name = oid_to_hex_r(merge_base_hex, &oid);
+       } else {
+               oidcpy(&oid, &ent->item->oid);
+               name = ent->name;
+       }
+
+       if (diff_cache(revs, &oid, name, cached))
                exit(128);
 
        diff_set_mnemonic_prefix(&revs->diffopt, "c/", cached ? "i/" : "w/");
diff --git a/diff.h b/diff.h
index fedfeab7a2615e90d3ecacb0ad194514fa9093ec..6c2efa16fde5e1436ad9c3c54d6c747ec8cb807d 100644 (file)
--- a/diff.h
+++ b/diff.h
@@ -589,6 +589,7 @@ void diff_get_merge_base(const struct rev_info *revs, struct object_id *mb);
 int run_diff_files(struct rev_info *revs, unsigned int option);
 
 #define DIFF_INDEX_CACHED 01
+#define DIFF_INDEX_MERGE_BASE 02
 int run_diff_index(struct rev_info *revs, unsigned int option);
 
 int do_diff_cache(const struct object_id *, struct diff_options *);
index bd4cf254d96de2c90b79c5ae56eb596451c2262e..49432379cb2c1e25755bb3c316d267e22ca744e4 100755 (executable)
@@ -97,4 +97,63 @@ test_expect_success 'diff --merge-base with three commits' '
        test_i18ngrep "usage" err
 '
 
+for cmd in diff-index diff
+do
+       test_expect_success "$cmd --merge-base with one commit" '
+               git checkout master &&
+               git $cmd commit-C >expect &&
+               git $cmd --merge-base br2 >actual &&
+               test_cmp expect actual
+       '
+
+       test_expect_success "$cmd --merge-base with one commit and unstaged changes" '
+               git checkout master &&
+               test_when_finished git reset --hard &&
+               echo unstaged >>c &&
+               git $cmd commit-C >expect &&
+               git $cmd --merge-base br2 >actual &&
+               test_cmp expect actual
+       '
+
+       test_expect_success "$cmd --merge-base with one commit and staged and unstaged changes" '
+               git checkout master &&
+               test_when_finished git reset --hard &&
+               echo staged >>c &&
+               git add c &&
+               echo unstaged >>c &&
+               git $cmd commit-C >expect &&
+               git $cmd --merge-base br2 >actual &&
+               test_cmp expect actual
+       '
+
+       test_expect_success "$cmd --merge-base --cached with one commit and staged and unstaged changes" '
+               git checkout master &&
+               test_when_finished git reset --hard &&
+               echo staged >>c &&
+               git add c &&
+               echo unstaged >>c &&
+               git $cmd --cached commit-C >expect &&
+               git $cmd --cached --merge-base br2 >actual &&
+               test_cmp expect actual
+       '
+
+       test_expect_success "$cmd --merge-base with non-commit" '
+               git checkout master &&
+               test_must_fail git $cmd --merge-base 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 one commit" '
+               git checkout master &&
+               test_must_fail git $cmd --merge-base br3 2>err &&
+               test_i18ngrep "fatal: no merge base found" err
+       '
+
+       test_expect_success "$cmd --merge-base with multiple merge bases and one commit" '
+               git checkout master &&
+               test_must_fail git $cmd --merge-base br1 2>err &&
+               test_i18ngrep "fatal: multiple merge bases found" err
+       '
+done
+
 test_done