Skip to content

Commit 0f5a1d4

Browse files
Denton-Lgitster
authored andcommitted
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 <[email protected]> Signed-off-by: Junio C Hamano <[email protected]>
1 parent df7dbab commit 0f5a1d4

File tree

7 files changed

+92
-6
lines changed

7 files changed

+92
-6
lines changed

Documentation/git-diff-index.txt

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ git-diff-index - Compare a tree to the working tree or index
99
SYNOPSIS
1010
--------
1111
[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>...]
1313

1414
DESCRIPTION
1515
-----------
@@ -29,6 +29,11 @@ include::diff-options.txt[]
2929
--cached::
3030
Do not consider the on-disk file at all.
3131

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+
3237
-m::
3338
By default, files recorded in the index but not checked
3439
out are reported as deleted. This flag makes

Documentation/git-diff.txt

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ SYNOPSIS
1010
--------
1111
[verse]
1212
'git diff' [<options>] [<commit>] [--] [<path>...]
13-
'git diff' [<options>] --cached [<commit>] [--] [<path>...]
13+
'git diff' [<options>] --cached [--merge-base] [<commit>] [--] [<path>...]
1414
'git diff' [<options>] <commit> [<commit>...] <commit> [--] [<path>...]
1515
'git diff' [<options>] <commit>...<commit> [--] [<path>...]
1616
'git diff' [<options>] <blob> <blob>
@@ -40,7 +40,7 @@ files on disk.
4040
or when running the command outside a working tree
4141
controlled by Git. This form implies `--exit-code`.
4242

43-
'git diff' [<options>] --cached [<commit>] [--] [<path>...]::
43+
'git diff' [<options>] --cached [--merge-base] [<commit>] [--] [<path>...]::
4444

4545
This form is to view the changes you staged for the next
4646
commit relative to the named <commit>. Typically you
@@ -49,6 +49,10 @@ files on disk.
4949
If HEAD does not exist (e.g. unborn branches) and
5050
<commit> is not given, it shows all staged changes.
5151
--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)`.
5256

5357
'git diff' [<options>] <commit> [--] [<path>...]::
5458

@@ -89,8 +93,8 @@ files on disk.
8993

9094
Just in case you are doing something exotic, it should be
9195
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>.
9498

9599
For a more complete list of ways to spell <commit>, see
96100
"SPECIFYING REVISIONS" section in linkgit:gitrevisions[7].

builtin/diff-index.c

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,8 @@ int cmd_diff_index(int argc, const char **argv, const char *prefix)
3333

3434
if (!strcmp(arg, "--cached"))
3535
option |= DIFF_INDEX_CACHED;
36+
else if (!strcmp(arg, "--merge-base"))
37+
option |= DIFF_INDEX_MERGE_BASE;
3638
else
3739
usage(diff_cache_usage);
3840
}

builtin/diff.c

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,8 @@ static int builtin_diff_index(struct rev_info *revs,
139139
const char *arg = argv[1];
140140
if (!strcmp(arg, "--cached") || !strcmp(arg, "--staged"))
141141
option |= DIFF_INDEX_CACHED;
142+
else if (!strcmp(arg, "--merge-base"))
143+
option |= DIFF_INDEX_MERGE_BASE;
142144
else
143145
usage(builtin_diff_usage);
144146
argv++; argc--;

diff-lib.c

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -561,13 +561,26 @@ int run_diff_index(struct rev_info *revs, unsigned int option)
561561
{
562562
struct object_array_entry *ent;
563563
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];
564568

565569
if (revs->pending.nr != 1)
566570
BUG("run_diff_index must be passed exactly one tree");
567571

568572
trace_performance_enter();
569573
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))
571584
exit(128);
572585

573586
diff_set_mnemonic_prefix(&revs->diffopt, "c/", cached ? "i/" : "w/");

diff.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -589,6 +589,7 @@ void diff_get_merge_base(const struct rev_info *revs, struct object_id *mb);
589589
int run_diff_files(struct rev_info *revs, unsigned int option);
590590

591591
#define DIFF_INDEX_CACHED 01
592+
#define DIFF_INDEX_MERGE_BASE 02
592593
int run_diff_index(struct rev_info *revs, unsigned int option);
593594

594595
int do_diff_cache(const struct object_id *, struct diff_options *);

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

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,4 +97,63 @@ test_expect_success 'diff --merge-base with three commits' '
9797
test_i18ngrep "usage" err
9898
'
9999

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+
100159
test_done

0 commit comments

Comments
 (0)