@matheusbsbs / matheus-bagliano / commits / 0f5a1d449b
Fork 0

builtin/diff-index: learn --merge-base

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>

Denton Liu committed Sep 20, 2020 at 04:22 UTC 0f5a1d449b9538c2765de9d6683abbb83a7fb4e2
7 files changed +92 -6
Documentation/git-diff-index.txt
+6 -1
@@ -9,7 +9,7 @@ git-diff-index - Compare a tree to the working tree or index
9 SYNOPSIS
10 --------
11 [verse]
12 -'git diff-index' [-m] [--cached] [<common diff options>] <tree-ish> [<path>...]
12 +'git diff-index' [-m] [--cached] [--merge-base] [<common diff options>] <tree-ish> [<path>...]
13
14 DESCRIPTION
15 -----------
@@ -29,6 +29,11 @@ include::diff-options.txt[]
29 --cached::
30 Do not consider the on-disk file at all.
31
32 +--merge-base::
33 + Instead of comparing <tree-ish> directly, use the merge base
34 + between <tree-ish> and HEAD instead. <tree-ish> must be a
35 + commit.
36 +
37 -m::
38 By default, files recorded in the index but not checked
39 out are reported as deleted. This flag makes
Documentation/git-diff.txt
+8 -4
@@ -10,7 +10,7 @@ SYNOPSIS
10 --------
11 [verse]
12 'git diff' [<options>] [<commit>] [--] [<path>...]
13 -'git diff' [<options>] --cached [<commit>] [--] [<path>...]
13 +'git diff' [<options>] --cached [--merge-base] [<commit>] [--] [<path>...]
14 'git diff' [<options>] <commit> [<commit>...] <commit> [--] [<path>...]
15 'git diff' [<options>] <commit>...<commit> [--] [<path>...]
16 'git diff' [<options>] <blob> <blob>
@@ -40,7 +40,7 @@ files on disk.
40 or when running the command outside a working tree
41 controlled by Git. This form implies `--exit-code`.
42
43 -'git diff' [<options>] --cached [<commit>] [--] [<path>...]::
43 +'git diff' [<options>] --cached [--merge-base] [<commit>] [--] [<path>...]::
44
45 This form is to view the changes you staged for the next
46 commit relative to the named <commit>. Typically you
@@ -49,6 +49,10 @@ files on disk.
49 If HEAD does not exist (e.g. unborn branches) and
50 <commit> is not given, it shows all staged changes.
51 --staged is a synonym of --cached.
52 ++
53 +If --merge-base is given, instead of using <commit>, use the merge base
54 +of <commit> and HEAD. `git diff --merge-base A` is equivalent to
55 +`git diff $(git merge-base A HEAD)`.
56
57 'git diff' [<options>] <commit> [--] [<path>...]::
58
@@ -89,8 +93,8 @@ files on disk.
93
94 Just in case you are doing something exotic, it should be
95 noted that all of the <commit> in the above description, except
92 -in the last two forms that use `..` notations, can be any
93 -<tree>.
96 +in the `--merge-base` case and in the last two forms that use `..`
97 +notations, can be any <tree>.
98
99 For a more complete list of ways to spell <commit>, see
100 "SPECIFYING REVISIONS" section in linkgit:gitrevisions[7].
builtin/diff-index.c
+2
@@ -33,6 +33,8 @@ int cmd_diff_index(int argc, const char **argv, const char *prefix)
33
34 if (!strcmp(arg, "--cached"))
35 option |= DIFF_INDEX_CACHED;
36 + else if (!strcmp(arg, "--merge-base"))
37 + option |= DIFF_INDEX_MERGE_BASE;
38 else
39 usage(diff_cache_usage);
40 }
builtin/diff.c
+2
@@ -139,6 +139,8 @@ static int builtin_diff_index(struct rev_info *revs,
139 const char *arg = argv[1];
140 if (!strcmp(arg, "--cached") || !strcmp(arg, "--staged"))
141 option |= DIFF_INDEX_CACHED;
142 + else if (!strcmp(arg, "--merge-base"))
143 + option |= DIFF_INDEX_MERGE_BASE;
144 else
145 usage(builtin_diff_usage);
146 argv++; argc--;
diff-lib.c
+14 -1
@@ -561,13 +561,26 @@ int run_diff_index(struct rev_info *revs, unsigned int option)
561 {
562 struct object_array_entry *ent;
563 int cached = !!(option & DIFF_INDEX_CACHED);
564 + int merge_base = !!(option & DIFF_INDEX_MERGE_BASE);
565 + struct object_id oid;
566 + const char *name;
567 + char merge_base_hex[GIT_MAX_HEXSZ + 1];
568
569 if (revs->pending.nr != 1)
570 BUG("run_diff_index must be passed exactly one tree");
571
572 trace_performance_enter();
573 ent = revs->pending.objects;
570 - if (diff_cache(revs, &ent->item->oid, ent->name, cached))
574 +
575 + if (merge_base) {
576 + diff_get_merge_base(revs, &oid);
577 + name = oid_to_hex_r(merge_base_hex, &oid);
578 + } else {
579 + oidcpy(&oid, &ent->item->oid);
580 + name = ent->name;
581 + }
582 +
583 + if (diff_cache(revs, &oid, name, cached))
584 exit(128);
585
586 diff_set_mnemonic_prefix(&revs->diffopt, "c/", cached ? "i/" : "w/");
diff.h
+1
@@ -589,6 +589,7 @@ void diff_get_merge_base(const struct rev_info *revs, struct object_id *mb);
589 int run_diff_files(struct rev_info *revs, unsigned int option);
590
591 #define DIFF_INDEX_CACHED 01
592 +#define DIFF_INDEX_MERGE_BASE 02
593 int run_diff_index(struct rev_info *revs, unsigned int option);
594
595 int do_diff_cache(const struct object_id *, struct diff_options *);
t/t4068-diff-symmetric-merge-base.sh
+59
@@ -97,4 +97,63 @@ test_expect_success 'diff --merge-base with three commits' '
97 test_i18ngrep "usage" err
98 '
99
100 +for cmd in diff-index diff
101 +do
102 + test_expect_success "$cmd --merge-base with one commit" '
103 + git checkout master &&
104 + git $cmd commit-C >expect &&
105 + git $cmd --merge-base br2 >actual &&
106 + test_cmp expect actual
107 + '
108 +
109 + test_expect_success "$cmd --merge-base with one commit and unstaged changes" '
110 + git checkout master &&
111 + test_when_finished git reset --hard &&
112 + echo unstaged >>c &&
113 + git $cmd commit-C >expect &&
114 + git $cmd --merge-base br2 >actual &&
115 + test_cmp expect actual
116 + '
117 +
118 + test_expect_success "$cmd --merge-base with one commit and staged and unstaged changes" '
119 + git checkout master &&
120 + test_when_finished git reset --hard &&
121 + echo staged >>c &&
122 + git add c &&
123 + echo unstaged >>c &&
124 + git $cmd commit-C >expect &&
125 + git $cmd --merge-base br2 >actual &&
126 + test_cmp expect actual
127 + '
128 +
129 + test_expect_success "$cmd --merge-base --cached with one commit and staged and unstaged changes" '
130 + git checkout master &&
131 + test_when_finished git reset --hard &&
132 + echo staged >>c &&
133 + git add c &&
134 + echo unstaged >>c &&
135 + git $cmd --cached commit-C >expect &&
136 + git $cmd --cached --merge-base br2 >actual &&
137 + test_cmp expect actual
138 + '
139 +
140 + test_expect_success "$cmd --merge-base with non-commit" '
141 + git checkout master &&
142 + test_must_fail git $cmd --merge-base master^{tree} 2>err &&
143 + test_i18ngrep "fatal: --merge-base only works with commits" err
144 + '
145 +
146 + test_expect_success "$cmd --merge-base with no merge bases and one commit" '
147 + git checkout master &&
148 + test_must_fail git $cmd --merge-base br3 2>err &&
149 + test_i18ngrep "fatal: no merge base found" err
150 + '
151 +
152 + test_expect_success "$cmd --merge-base with multiple merge bases and one commit" '
153 + git checkout master &&
154 + test_must_fail git $cmd --merge-base br1 2>err &&
155 + test_i18ngrep "fatal: multiple merge bases found" err
156 + '
157 +done
158 +
159 test_done