Merge branch 'dl/diff-merge-base'

"git diff A...B" learned "git diff --merge-base A B", which is a
longer short-hand to say the same thing.

* dl/diff-merge-base:
  contrib/completion: complete `git diff --merge-base`
  builtin/diff-tree: learn --merge-base
  builtin/diff-index: learn --merge-base
  t4068: add --merge-base tests
  diff-lib: define diff_get_merge_base()
  diff-lib: accept option flags in run_diff_index()
  contrib/completion: extract common diff/difftool options
  git-diff.txt: backtick quote command text
  git-diff-index.txt: make --cached description a proper sentence
  t4068: remove unnecessary >tmp
This commit is contained in:
Junio C Hamano 2020-11-02 13:17:39 -08:00
commit b6fb70c985
11 changed files with 356 additions and 141 deletions

View File

@ -9,7 +9,7 @@ git-diff-index - Compare a tree to the working tree or index
SYNOPSIS SYNOPSIS
-------- --------
[verse] [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 DESCRIPTION
----------- -----------
@ -27,7 +27,12 @@ include::diff-options.txt[]
The id of a tree object to diff against. The id of a tree object to diff against.
--cached:: --cached::
do not consider the on-disk file at all 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:: -m::
By default, files recorded in the index but not checked By default, files recorded in the index but not checked

View File

@ -10,7 +10,7 @@ SYNOPSIS
-------- --------
[verse] [verse]
'git diff-tree' [--stdin] [-m] [-s] [-v] [--no-commit-id] [--pretty] '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>...] [<common diff options>] <tree-ish> [<tree-ish>] [<path>...]
DESCRIPTION DESCRIPTION
@ -43,6 +43,11 @@ include::diff-options.txt[]
When `--root` is specified the initial commit will be shown as a big 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. 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:: --stdin::
When `--stdin` is specified, the command does not take When `--stdin` is specified, the command does not take
<tree-ish> arguments from the command line. Instead, it <tree-ish> arguments from the command line. Instead, it

View File

@ -10,8 +10,8 @@ SYNOPSIS
-------- --------
[verse] [verse]
'git diff' [<options>] [<commit>] [--] [<path>...] '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>] [--merge-base] <commit> [<commit>...] <commit> [--] [<path>...]
'git diff' [<options>] <commit>...<commit> [--] [<path>...] 'git diff' [<options>] <commit>...<commit> [--] [<path>...]
'git diff' [<options>] <blob> <blob> 'git diff' [<options>] <blob> <blob>
'git diff' [<options>] --no-index [--] <path> <path> 'git diff' [<options>] --no-index [--] <path> <path>
@ -40,7 +40,7 @@ files on disk.
or when running the command outside a working tree or when running the command outside a working tree
controlled by Git. This form implies `--exit-code`. 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 This form is to view the changes you staged for the next
commit relative to the named <commit>. Typically you 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 If HEAD does not exist (e.g. unborn branches) and
<commit> is not given, it shows all staged changes. <commit> is not given, it shows all staged changes.
--staged is a synonym of --cached. --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>...]:: 'git diff' [<options>] <commit> [--] [<path>...]::
@ -58,23 +62,27 @@ files on disk.
branch name to compare with the tip of a different branch name to compare with the tip of a different
branch. branch.
'git diff' [<options>] <commit> <commit> [--] [<path>...]:: 'git diff' [<options>] [--merge-base] <commit> <commit> [--] [<path>...]::
This is to view the changes between two arbitrary This is to view the changes between two arbitrary
<commit>. <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>...]:: 'git diff' [<options>] <commit> <commit>... <commit> [--] [<path>...]::
This form is to view the results of a merge commit. The first This form is to view the results of a merge commit. The first
listed <commit> must be the merge itself; the remaining two or listed <commit> must be the merge itself; the remaining two or
more commits should be its parents. A convenient way to produce more commits should be its parents. A convenient way to produce
the desired set of revisions is to use the {caret}@ suffix. the desired set of revisions is to use the `^@` suffix.
For instance, if `master` names a merge commit, `git diff master For instance, if `master` names a merge commit, `git diff master
master^@` gives the same combined diff as `git show master`. master^@` gives the same combined diff as `git show master`.
'git diff' [<options>] <commit>..<commit> [--] [<path>...]:: 'git diff' [<options>] <commit>..<commit> [--] [<path>...]::
This is synonymous to the earlier form (without the "..") for This is synonymous to the earlier form (without the `..`) for
viewing the changes between two arbitrary <commit>. If <commit> on viewing the changes between two arbitrary <commit>. If <commit> on
one side is omitted, it will have the same effect as one side is omitted, it will have the same effect as
using HEAD instead. using HEAD instead.
@ -83,20 +91,20 @@ files on disk.
This form is to view the changes on the branch containing This form is to view the changes on the branch containing
and up to the second <commit>, starting at a common ancestor and up to the second <commit>, starting at a common ancestor
of both <commit>. "git diff A\...B" is equivalent to of both <commit>. `git diff A...B` is equivalent to
"git diff $(git merge-base A B) B". You can omit any one `git diff $(git merge-base A B) B`. You can omit any one
of <commit>, which has the same effect as using HEAD instead. of <commit>, which has the same effect as using HEAD instead.
Just in case you are doing something exotic, it should be Just in case you are doing something exotic, it should be
noted that all of the <commit> in the above description, except noted that all of the <commit> in the above description, except
in the last two forms that use ".." notations, can be any in the `--merge-base` case and in the last two forms that use `..`
<tree>. notations, can be any <tree>.
For a more complete list of ways to spell <commit>, see For a more complete list of ways to spell <commit>, see
"SPECIFYING REVISIONS" section in linkgit:gitrevisions[7]. "SPECIFYING REVISIONS" section in linkgit:gitrevisions[7].
However, "diff" is about comparing two _endpoints_, not ranges, However, "diff" is about comparing two _endpoints_, not ranges,
and the range notations ("<commit>..<commit>" and and the range notations (`<commit>..<commit>` and
"<commit>\...<commit>") do not mean a range as defined in the `<commit>...<commit>`) do not mean a range as defined in the
"SPECIFYING RANGES" section in linkgit:gitrevisions[7]. "SPECIFYING RANGES" section in linkgit:gitrevisions[7].
'git diff' [<options>] <blob> <blob>:: 'git diff' [<options>] <blob> <blob>::
@ -144,9 +152,9 @@ $ git diff HEAD <3>
+ +
<1> Changes in the working tree not yet staged for the next commit. <1> Changes in the working tree not yet staged for the next commit.
<2> Changes between the index and your last commit; what you <2> Changes between the index and your last commit; what you
would be committing if you run "git commit" without "-a" option. would be committing if you run `git commit` without `-a` option.
<3> Changes in the working tree since your last commit; what you <3> Changes in the working tree since your last commit; what you
would be committing if you run "git commit -a" would be committing if you run `git commit -a`
Comparing with arbitrary commits:: Comparing with arbitrary commits::
+ +

View File

@ -15,7 +15,7 @@ COMMON_DIFF_OPTIONS_HELP;
int cmd_diff_index(int argc, const char **argv, const char *prefix) int cmd_diff_index(int argc, const char **argv, const char *prefix)
{ {
struct rev_info rev; struct rev_info rev;
int cached = 0; unsigned int option = 0;
int i; int i;
int result; int result;
@ -32,7 +32,9 @@ int cmd_diff_index(int argc, const char **argv, const char *prefix)
const char *arg = argv[i]; const char *arg = argv[i];
if (!strcmp(arg, "--cached")) if (!strcmp(arg, "--cached"))
cached = 1; option |= DIFF_INDEX_CACHED;
else if (!strcmp(arg, "--merge-base"))
option |= DIFF_INDEX_MERGE_BASE;
else else
usage(diff_cache_usage); usage(diff_cache_usage);
} }
@ -46,7 +48,7 @@ int cmd_diff_index(int argc, const char **argv, const char *prefix)
if (rev.pending.nr != 1 || if (rev.pending.nr != 1 ||
rev.max_count != -1 || rev.min_age != -1 || rev.max_age != -1) rev.max_count != -1 || rev.min_age != -1 || rev.max_age != -1)
usage(diff_cache_usage); usage(diff_cache_usage);
if (!cached) { if (!(option & DIFF_INDEX_CACHED)) {
setup_work_tree(); setup_work_tree();
if (read_cache_preload(&rev.diffopt.pathspec) < 0) { if (read_cache_preload(&rev.diffopt.pathspec) < 0) {
perror("read_cache_preload"); perror("read_cache_preload");
@ -56,7 +58,7 @@ int cmd_diff_index(int argc, const char **argv, const char *prefix)
perror("read_cache"); perror("read_cache");
return -1; return -1;
} }
result = run_diff_index(&rev, cached); result = run_diff_index(&rev, option);
UNLEAK(rev); UNLEAK(rev);
return diff_result_code(&rev.diffopt, result); return diff_result_code(&rev.diffopt, result);
} }

View 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 setup_revision_opt s_r_opt;
struct userformat_want w; struct userformat_want w;
int read_stdin = 0; int read_stdin = 0;
int merge_base = 0;
if (argc == 2 && !strcmp(argv[1], "-h")) if (argc == 2 && !strcmp(argv[1], "-h"))
usage(diff_tree_usage); usage(diff_tree_usage);
@ -143,9 +144,18 @@ int cmd_diff_tree(int argc, const char **argv, const char *prefix)
read_stdin = 1; read_stdin = 1;
continue; continue;
} }
if (!strcmp(arg, "--merge-base")) {
merge_base = 1;
continue;
}
usage(diff_tree_usage); 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 * NOTE! We expect "a..b" to expand to "^a b" but it is
* perfectly valid for revision range parser to yield "b ^a", * 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: case 2:
tree1 = opt->pending.objects[0].item; tree1 = opt->pending.objects[0].item;
tree2 = opt->pending.objects[1].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); SWAP(tree2, tree1);
} }
diff_tree_oid(&tree1->oid, &tree2->oid, "", &opt->diffopt); diff_tree_oid(&tree1->oid, &tree2->oid, "", &opt->diffopt);

View File

@ -26,7 +26,7 @@
static const char builtin_diff_usage[] = static const char builtin_diff_usage[] =
"git diff [<options>] [<commit>] [--] [<path>...]\n" "git diff [<options>] [<commit>] [--] [<path>...]\n"
" or: git diff [<options>] --cached [<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>] <commit>...<commit>] [--] [<path>...]\n"
" or: git diff [<options>] <blob> <blob>]\n" " or: git diff [<options>] <blob> <blob>]\n"
" or: git diff [<options>] --no-index [--] <path> <path>]\n" " or: git diff [<options>] --no-index [--] <path> <path>]\n"
@ -134,11 +134,13 @@ static int builtin_diff_blobs(struct rev_info *revs,
static int builtin_diff_index(struct rev_info *revs, static int builtin_diff_index(struct rev_info *revs,
int argc, const char **argv) int argc, const char **argv)
{ {
int cached = 0; unsigned int option = 0;
while (1 < argc) { while (1 < argc) {
const char *arg = argv[1]; const char *arg = argv[1];
if (!strcmp(arg, "--cached") || !strcmp(arg, "--staged")) if (!strcmp(arg, "--cached") || !strcmp(arg, "--staged"))
cached = 1; option |= DIFF_INDEX_CACHED;
else if (!strcmp(arg, "--merge-base"))
option |= DIFF_INDEX_MERGE_BASE;
else else
usage(builtin_diff_usage); usage(builtin_diff_usage);
argv++; argc--; argv++; argc--;
@ -151,7 +153,7 @@ static int builtin_diff_index(struct rev_info *revs,
revs->max_count != -1 || revs->min_age != -1 || revs->max_count != -1 || revs->min_age != -1 ||
revs->max_age != -1) revs->max_age != -1)
usage(builtin_diff_usage); usage(builtin_diff_usage);
if (!cached) { if (!(option & DIFF_INDEX_CACHED)) {
setup_work_tree(); setup_work_tree();
if (read_cache_preload(&revs->diffopt.pathspec) < 0) { if (read_cache_preload(&revs->diffopt.pathspec) < 0) {
perror("read_cache_preload"); perror("read_cache_preload");
@ -161,7 +163,7 @@ static int builtin_diff_index(struct rev_info *revs,
perror("read_cache"); perror("read_cache");
return -1; return -1;
} }
return run_diff_index(revs, cached); return run_diff_index(revs, option);
} }
static int builtin_diff_tree(struct rev_info *revs, static int builtin_diff_tree(struct rev_info *revs,
@ -170,19 +172,34 @@ static int builtin_diff_tree(struct rev_info *revs,
struct object_array_entry *ent1) struct object_array_entry *ent1)
{ {
const struct object_id *(oid[2]); const struct object_id *(oid[2]);
int swap = 0; struct object_id mb_oid;
int merge_base = 0;
if (argc > 1) while (1 < argc) {
usage(builtin_diff_usage); const char *arg = argv[1];
if (!strcmp(arg, "--merge-base"))
merge_base = 1;
else
usage(builtin_diff_usage);
argv++; argc--;
}
/* if (merge_base) {
* We saw two trees, ent0 and ent1. If ent1 is uninteresting, diff_get_merge_base(revs, &mb_oid);
* swap them. oid[0] = &mb_oid;
*/ oid[1] = &revs->pending.objects[1].item->oid;
if (ent1->item->flags & UNINTERESTING) } else {
swap = 1; int swap = 0;
oid[swap] = &ent0->item->oid;
oid[1 - swap] = &ent1->item->oid; /*
* 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); diff_tree_oid(oid[0], oid[1], "", &revs->diffopt);
log_tree_diff_flush(revs); log_tree_diff_flush(revs);
return 0; return 0;

View File

@ -1698,6 +1698,10 @@ __git_diff_common_options="--stat --numstat --shortstat --summary
--patch --no-patch --patch --no-patch
" "
__git_diff_difftool_options="--cached --staged --pickaxe-all --pickaxe-regex
--base --ours --theirs --no-index --relative --merge-base
$__git_diff_common_options"
_git_diff () _git_diff ()
{ {
__git_has_doubledash && return __git_has_doubledash && return
@ -1720,10 +1724,7 @@ _git_diff ()
return return
;; ;;
--*) --*)
__gitcomp "--cached --staged --pickaxe-all --pickaxe-regex __gitcomp "$__git_diff_difftool_options"
--base --ours --theirs --no-index
$__git_diff_common_options
"
return return
;; ;;
esac esac
@ -1745,11 +1746,7 @@ _git_difftool ()
return return
;; ;;
--*) --*)
__gitcomp_builtin difftool "$__git_diff_common_options __gitcomp_builtin difftool "$__git_diff_difftool_options"
--base --cached --ours --theirs
--pickaxe-all --pickaxe-regex
--relative --staged
"
return return
;; ;;
esac esac

View File

@ -13,6 +13,7 @@
#include "submodule.h" #include "submodule.h"
#include "dir.h" #include "dir.h"
#include "fsmonitor.h" #include "fsmonitor.h"
#include "commit-reach.h"
/* /*
* diff-files * diff-files
@ -510,16 +511,74 @@ static int diff_cache(struct rev_info *revs,
return unpack_trees(1, &t, &opts); return unpack_trees(1, &t, &opts);
} }
int run_diff_index(struct rev_info *revs, int cached) void diff_get_merge_base(const struct rev_info *revs, struct object_id *mb)
{
int i;
struct commit *mb_child[2] = {0};
struct commit_list *merge_bases;
for (i = 0; i < revs->pending.nr; i++) {
struct object *obj = revs->pending.objects[i].item;
if (obj->flags)
die(_("--merge-base does not work with ranges"));
if (obj->type != OBJ_COMMIT)
die(_("--merge-base only works with commits"));
}
/*
* This check must go after the for loop above because A...B
* ranges produce three pending commits, resulting in a
* misleading error message.
*/
if (revs->pending.nr < 1 || revs->pending.nr > 2)
BUG("unexpected revs->pending.nr: %d", revs->pending.nr);
for (i = 0; i < revs->pending.nr; i++)
mb_child[i] = lookup_commit_reference(the_repository, &revs->pending.objects[i].item->oid);
if (revs->pending.nr == 1) {
struct object_id oid;
if (get_oid("HEAD", &oid))
die(_("unable to get HEAD"));
mb_child[1] = lookup_commit_reference(the_repository, &oid);
}
merge_bases = repo_get_merge_bases(the_repository, mb_child[0], mb_child[1]);
if (!merge_bases)
die(_("no merge base found"));
if (merge_bases->next)
die(_("multiple merge bases found"));
oidcpy(mb, &merge_bases->item->object.oid);
free_commit_list(merge_bases);
}
int run_diff_index(struct rev_info *revs, unsigned int option)
{ {
struct object_array_entry *ent; 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) if (revs->pending.nr != 1)
BUG("run_diff_index must be passed exactly one tree"); BUG("run_diff_index must be passed exactly one tree");
trace_performance_enter(); trace_performance_enter();
ent = revs->pending.objects; 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); exit(128);
diff_set_mnemonic_prefix(&revs->diffopt, "c/", cached ? "i/" : "w/"); diff_set_mnemonic_prefix(&revs->diffopt, "c/", cached ? "i/" : "w/");

7
diff.h
View File

@ -578,12 +578,17 @@ void diff_warn_rename_limit(const char *varname, int needed, int degraded_cc);
*/ */
const char *diff_aligned_abbrev(const struct object_id *sha1, int); const char *diff_aligned_abbrev(const struct object_id *sha1, int);
void diff_get_merge_base(const struct rev_info *revs, struct object_id *mb);
/* do not report anything on removed paths */ /* do not report anything on removed paths */
#define DIFF_SILENT_ON_REMOVED 01 #define DIFF_SILENT_ON_REMOVED 01
/* report racily-clean paths as modified */ /* report racily-clean paths as modified */
#define DIFF_RACY_IS_MODIFIED 02 #define DIFF_RACY_IS_MODIFIED 02
int run_diff_files(struct rev_info *revs, unsigned int option); int run_diff_files(struct rev_info *revs, unsigned int option);
int run_diff_index(struct rev_info *revs, int cached);
#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 *); int do_diff_cache(const struct object_id *, struct diff_options *);
int diff_flush_patch_id(struct diff_options *, struct object_id *, int, int); int diff_flush_patch_id(struct diff_options *, struct object_id *, int, int);

View File

@ -0,0 +1,193 @@
#!/bin/sh
test_description='behavior of diff with symmetric-diff setups and --merge-base'
. ./test-lib.sh
# build these situations:
# - normal merge with one merge base (br1...b2r);
# - criss-cross merge ie 2 merge bases (br1...master);
# - disjoint subgraph (orphan branch, br3...master).
#
# B---E <-- master
# / \ /
# A X
# \ / \
# C---D--G <-- br1
# \ /
# ---F <-- br2
#
# H <-- br3
#
# We put files into a few commits so that we can verify the
# output as well.
test_expect_success setup '
git commit --allow-empty -m A &&
echo b >b &&
git add b &&
git commit -m B &&
git checkout -b br1 HEAD^ &&
echo c >c &&
git add c &&
git commit -m C &&
git tag commit-C &&
git merge -m D master &&
git tag commit-D &&
git checkout master &&
git merge -m E commit-C &&
git checkout -b br2 commit-C &&
echo f >f &&
git add f &&
git commit -m F &&
git checkout br1 &&
git merge -m G br2 &&
git checkout --orphan br3 &&
git commit -m H
'
test_expect_success 'diff with one merge base' '
git diff commit-D...br1 >tmp &&
tail -n 1 tmp >actual &&
echo +f >expect &&
test_cmp expect actual
'
# The output (in tmp) can have +b or +c depending
# on which merge base (commit B or C) is picked.
# It should have one of those two, which comes out
# to seven lines.
test_expect_success 'diff with two merge bases' '
git diff br1...master >tmp 2>err &&
test_line_count = 7 tmp &&
test_line_count = 1 err
'
test_expect_success 'diff with no merge bases' '
test_must_fail git diff br2...br3 2>err &&
test_i18ngrep "fatal: br2...br3: no merge base" err
'
test_expect_success 'diff with too many symmetric differences' '
test_must_fail git diff br1...master br2...br3 2>err &&
test_i18ngrep "usage" err
'
test_expect_success 'diff with symmetric difference and extraneous arg' '
test_must_fail git diff master br1...master 2>err &&
test_i18ngrep "usage" err
'
test_expect_success 'diff with two ranges' '
test_must_fail git diff master br1..master br2..br3 2>err &&
test_i18ngrep "usage" err
'
test_expect_success 'diff with ranges and extra arg' '
test_must_fail git diff master br1..master commit-D 2>err &&
test_i18ngrep "usage" err
'
test_expect_success 'diff --merge-base with no commits' '
test_must_fail git diff --merge-base
'
test_expect_success 'diff --merge-base with three commits' '
test_must_fail git diff --merge-base br1 br2 master 2>err &&
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
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

View File

@ -1,91 +0,0 @@
#!/bin/sh
test_description='behavior of diff with symmetric-diff setups'
. ./test-lib.sh
# build these situations:
# - normal merge with one merge base (br1...b2r);
# - criss-cross merge ie 2 merge bases (br1...master);
# - disjoint subgraph (orphan branch, br3...master).
#
# B---E <-- master
# / \ /
# A X
# \ / \
# C---D--G <-- br1
# \ /
# ---F <-- br2
#
# H <-- br3
#
# We put files into a few commits so that we can verify the
# output as well.
test_expect_success setup '
git commit --allow-empty -m A &&
echo b >b &&
git add b &&
git commit -m B &&
git checkout -b br1 HEAD^ &&
echo c >c &&
git add c &&
git commit -m C &&
git tag commit-C &&
git merge -m D master &&
git tag commit-D &&
git checkout master &&
git merge -m E commit-C &&
git checkout -b br2 commit-C &&
echo f >f &&
git add f &&
git commit -m F &&
git checkout br1 &&
git merge -m G br2 &&
git checkout --orphan br3 &&
git commit -m H
'
test_expect_success 'diff with one merge base' '
git diff commit-D...br1 >tmp &&
tail -n 1 tmp >actual &&
echo +f >expect &&
test_cmp expect actual
'
# The output (in tmp) can have +b or +c depending
# on which merge base (commit B or C) is picked.
# It should have one of those two, which comes out
# to seven lines.
test_expect_success 'diff with two merge bases' '
git diff br1...master >tmp 2>err &&
test_line_count = 7 tmp &&
test_line_count = 1 err
'
test_expect_success 'diff with no merge bases' '
test_must_fail git diff br2...br3 >tmp 2>err &&
test_i18ngrep "fatal: br2...br3: no merge base" err
'
test_expect_success 'diff with too many symmetric differences' '
test_must_fail git diff br1...master br2...br3 >tmp 2>err &&
test_i18ngrep "usage" err
'
test_expect_success 'diff with symmetric difference and extraneous arg' '
test_must_fail git diff master br1...master >tmp 2>err &&
test_i18ngrep "usage" err
'
test_expect_success 'diff with two ranges' '
test_must_fail git diff master br1..master br2..br3 >tmp 2>err &&
test_i18ngrep "usage" err
'
test_expect_success 'diff with ranges and extra arg' '
test_must_fail git diff master br1..master commit-D >tmp 2>err &&
test_i18ngrep "usage" err
'
test_done