From 8023a5e85bffa969dffab3a509a4c62b2c8906d5 Mon Sep 17 00:00:00 2001 From: Denton Liu Date: Thu, 17 Sep 2020 00:44:05 -0700 Subject: t4068: remove unnecessary >tmp The many `git diff` invocations have a `>tmp` redirection even though the file is not being used afterwards. Remove these unnecessary redirections. Signed-off-by: Denton Liu Signed-off-by: Junio C Hamano diff --git a/t/t4068-diff-symmetric.sh b/t/t4068-diff-symmetric.sh index 31d17a5..60c506c 100755 --- a/t/t4068-diff-symmetric.sh +++ b/t/t4068-diff-symmetric.sh @@ -64,27 +64,27 @@ test_expect_success 'diff with two merge bases' ' ' test_expect_success 'diff with no merge bases' ' - test_must_fail git diff br2...br3 >tmp 2>err && + 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 >tmp 2>err && + 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 >tmp 2>err && + 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 >tmp 2>err && + 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 >tmp 2>err && + test_must_fail git diff master br1..master commit-D 2>err && test_i18ngrep "usage" err ' -- cgit v0.10.2-6-g49f6 From b277b737d987b272487f95a6e2afb22edd203553 Mon Sep 17 00:00:00 2001 From: Denton Liu Date: Thu, 17 Sep 2020 00:44:06 -0700 Subject: git-diff-index.txt: make --cached description a proper sentence Signed-off-by: Denton Liu Signed-off-by: Junio C Hamano diff --git a/Documentation/git-diff-index.txt b/Documentation/git-diff-index.txt index f4bd815..25fe165 100644 --- a/Documentation/git-diff-index.txt +++ b/Documentation/git-diff-index.txt @@ -27,7 +27,7 @@ include::diff-options.txt[] The id of a tree object to diff against. --cached:: - do not consider the on-disk file at all + Do not consider the on-disk file at all. -m:: By default, files recorded in the index but not checked -- cgit v0.10.2-6-g49f6 From a8fa6a08f4ff3ab99f99dd7ae989bd825071def3 Mon Sep 17 00:00:00 2001 From: Denton Liu Date: Thu, 17 Sep 2020 00:44:07 -0700 Subject: git-diff.txt: backtick quote command text The modern way to quote commands in the documentation is to use backticks instead of double-quotes as this renders the text with the code style. Convert double-quoted command text to backtick-quoted commands. While we're at it, quote one instance of `^@`. Signed-off-by: Denton Liu Signed-off-by: Junio C Hamano diff --git a/Documentation/git-diff.txt b/Documentation/git-diff.txt index 727f24d..8f7b4ed 100644 --- a/Documentation/git-diff.txt +++ b/Documentation/git-diff.txt @@ -68,13 +68,13 @@ files on disk. This form is to view the results of a merge commit. The first listed must be the merge itself; the remaining two or 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 master^@` gives the same combined diff as `git show master`. 'git diff' [] .. [--] [...]:: - 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 . If on one side is omitted, it will have the same effect as using HEAD instead. @@ -83,20 +83,20 @@ files on disk. This form is to view the changes on the branch containing and up to the second , starting at a common ancestor - of both . "git diff A\...B" is equivalent to - "git diff $(git merge-base A B) B". You can omit any one + of both . `git diff A...B` is equivalent to + `git diff $(git merge-base A B) B`. You can omit any one of , which has the same effect as using HEAD instead. Just in case you are doing something exotic, it should be noted that all of the in the above description, except -in the last two forms that use ".." notations, can be any +in the last two forms that use `..` notations, can be any . For a more complete list of ways to spell , see "SPECIFYING REVISIONS" section in linkgit:gitrevisions[7]. However, "diff" is about comparing two _endpoints_, not ranges, -and the range notations (".." and -"\...") do not mean a range as defined in the +and the range notations (`..` and +`...`) do not mean a range as defined in the "SPECIFYING RANGES" section in linkgit:gitrevisions[7]. 'git diff' [] :: @@ -144,9 +144,9 @@ $ git diff HEAD <3> + <1> Changes in the working tree not yet staged for the next commit. <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 - would be committing if you run "git commit -a" + would be committing if you run `git commit -a` Comparing with arbitrary commits:: + -- cgit v0.10.2-6-g49f6 From 308d7a7dc99043c90a9eff6916cad1abe0d473aa Mon Sep 17 00:00:00 2001 From: Denton Liu Date: Thu, 17 Sep 2020 00:44:08 -0700 Subject: contrib/completion: extract common diff/difftool options difftool parses its own options and then passes the remaining options onto diff. As a result, they share common command-line options. Instead of duplicating the list, use a shared $__git_diff_difftool_options list. The completion for diff is missing --relative and the completion for difftool is missing --no-index. Add both of these to the common list. Signed-off-by: Denton Liu Signed-off-by: Junio C Hamano diff --git a/contrib/completion/git-completion.bash b/contrib/completion/git-completion.bash index 9147fba..f68c8e0 100644 --- a/contrib/completion/git-completion.bash +++ b/contrib/completion/git-completion.bash @@ -1691,6 +1691,10 @@ __git_diff_common_options="--stat --numstat --shortstat --summary --textconv --no-textconv " +__git_diff_difftool_options="--cached --staged --pickaxe-all --pickaxe-regex + --base --ours --theirs --no-index --relative + $__git_diff_common_options" + _git_diff () { __git_has_doubledash && return @@ -1713,10 +1717,7 @@ _git_diff () return ;; --*) - __gitcomp "--cached --staged --pickaxe-all --pickaxe-regex - --base --ours --theirs --no-index - $__git_diff_common_options - " + __gitcomp "$__git_diff_difftool_options" return ;; esac @@ -1738,11 +1739,7 @@ _git_difftool () return ;; --*) - __gitcomp_builtin difftool "$__git_diff_common_options - --base --cached --ours --theirs - --pickaxe-all --pickaxe-regex - --relative --staged - " + __gitcomp_builtin difftool "$__git_diff_difftool_options" return ;; esac -- cgit v0.10.2-6-g49f6 From 4c3fe82ef17a6636c742b95cd292b83b02876e08 Mon Sep 17 00:00:00 2001 From: Denton Liu Date: Sun, 20 Sep 2020 04:22:22 -0700 Subject: diff-lib: accept option flags in run_diff_index() In a future commit, we will teach run_diff_index() to accept more options via flag bits. For now, change `cached` into a flag in the `option` bitfield. The behaviour should remain exactly the same. Signed-off-by: Denton Liu Signed-off-by: Junio C Hamano diff --git a/builtin/diff-index.c b/builtin/diff-index.c index 93ec642..c3878f7 100644 --- a/builtin/diff-index.c +++ b/builtin/diff-index.c @@ -15,7 +15,7 @@ COMMON_DIFF_OPTIONS_HELP; int cmd_diff_index(int argc, const char **argv, const char *prefix) { struct rev_info rev; - int cached = 0; + unsigned int option = 0; int i; int result; @@ -32,7 +32,7 @@ int cmd_diff_index(int argc, const char **argv, const char *prefix) const char *arg = argv[i]; if (!strcmp(arg, "--cached")) - cached = 1; + option |= DIFF_INDEX_CACHED; else usage(diff_cache_usage); } @@ -46,7 +46,7 @@ int cmd_diff_index(int argc, const char **argv, const char *prefix) if (rev.pending.nr != 1 || rev.max_count != -1 || rev.min_age != -1 || rev.max_age != -1) usage(diff_cache_usage); - if (!cached) { + if (!(option & DIFF_INDEX_CACHED)) { setup_work_tree(); if (read_cache_preload(&rev.diffopt.pathspec) < 0) { perror("read_cache_preload"); @@ -56,7 +56,7 @@ int cmd_diff_index(int argc, const char **argv, const char *prefix) perror("read_cache"); return -1; } - result = run_diff_index(&rev, cached); + result = run_diff_index(&rev, option); UNLEAK(rev); return diff_result_code(&rev.diffopt, result); } diff --git a/builtin/diff.c b/builtin/diff.c index cb98811..e45e19e 100644 --- a/builtin/diff.c +++ b/builtin/diff.c @@ -134,11 +134,11 @@ static int builtin_diff_blobs(struct rev_info *revs, static int builtin_diff_index(struct rev_info *revs, int argc, const char **argv) { - int cached = 0; + unsigned int option = 0; while (1 < argc) { const char *arg = argv[1]; if (!strcmp(arg, "--cached") || !strcmp(arg, "--staged")) - cached = 1; + option |= DIFF_INDEX_CACHED; else usage(builtin_diff_usage); argv++; argc--; @@ -151,7 +151,7 @@ static int builtin_diff_index(struct rev_info *revs, revs->max_count != -1 || revs->min_age != -1 || revs->max_age != -1) usage(builtin_diff_usage); - if (!cached) { + if (!(option & DIFF_INDEX_CACHED)) { setup_work_tree(); if (read_cache_preload(&revs->diffopt.pathspec) < 0) { perror("read_cache_preload"); @@ -161,7 +161,7 @@ static int builtin_diff_index(struct rev_info *revs, perror("read_cache"); return -1; } - return run_diff_index(revs, cached); + return run_diff_index(revs, option); } static int builtin_diff_tree(struct rev_info *revs, diff --git a/diff-lib.c b/diff-lib.c index 5d5d3da..ed72085 100644 --- a/diff-lib.c +++ b/diff-lib.c @@ -512,9 +512,10 @@ static int diff_cache(struct rev_info *revs, return unpack_trees(1, &t, &opts); } -int run_diff_index(struct rev_info *revs, int cached) +int run_diff_index(struct rev_info *revs, unsigned int option) { struct object_array_entry *ent; + int cached = !!(option & DIFF_INDEX_CACHED); if (revs->pending.nr != 1) BUG("run_diff_index must be passed exactly one tree"); diff --git a/diff.h b/diff.h index e0c0af6..aea0d5b 100644 --- a/diff.h +++ b/diff.h @@ -585,7 +585,9 @@ const char *diff_aligned_abbrev(const struct object_id *sha1, int); /* report racily-clean paths as modified */ #define DIFF_RACY_IS_MODIFIED 02 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 +int run_diff_index(struct rev_info *revs, unsigned int option); int do_diff_cache(const struct object_id *, struct diff_options *); int diff_flush_patch_id(struct diff_options *, struct object_id *, int, int); -- cgit v0.10.2-6-g49f6 From 177a8302683da62a62b5b03e8192fcfa897db750 Mon Sep 17 00:00:00 2001 From: Denton Liu Date: Sun, 20 Sep 2020 04:22:23 -0700 Subject: diff-lib: define diff_get_merge_base() In a future commit, we will be using this function to implement --merge-base functionality in various diff commands. Signed-off-by: Denton Liu Signed-off-by: Junio C Hamano diff --git a/diff-lib.c b/diff-lib.c index ed72085..468e3fe 100644 --- a/diff-lib.c +++ b/diff-lib.c @@ -13,6 +13,7 @@ #include "submodule.h" #include "dir.h" #include "fsmonitor.h" +#include "commit-reach.h" /* * diff-files @@ -512,6 +513,50 @@ static int diff_cache(struct rev_info *revs, return unpack_trees(1, &t, &opts); } +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; diff --git a/diff.h b/diff.h index aea0d5b..fedfeab 100644 --- a/diff.h +++ b/diff.h @@ -580,6 +580,8 @@ void diff_warn_rename_limit(const char *varname, int needed, int degraded_cc); */ 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 */ #define DIFF_SILENT_ON_REMOVED 01 /* report racily-clean paths as modified */ -- cgit v0.10.2-6-g49f6 From df7dbab881c1aa697f0826d8d00f73d43815acf5 Mon Sep 17 00:00:00 2001 From: Denton Liu Date: Sun, 20 Sep 2020 04:22:24 -0700 Subject: t4068: add --merge-base tests In the future, we will be adding more --merge-base tests to this test script. To prepare for that, rename the script accordingly and update its description. Also, add two basic --merge-base tests that don't require any functionality to be implemented yet. Signed-off-by: Denton Liu Signed-off-by: Junio C Hamano diff --git a/t/t4068-diff-symmetric-merge-base.sh b/t/t4068-diff-symmetric-merge-base.sh new file mode 100755 index 0000000..bd4cf25 --- /dev/null +++ b/t/t4068-diff-symmetric-merge-base.sh @@ -0,0 +1,100 @@ +#!/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 +' + +test_done diff --git a/t/t4068-diff-symmetric.sh b/t/t4068-diff-symmetric.sh deleted file mode 100755 index 60c506c..0000000 --- a/t/t4068-diff-symmetric.sh +++ /dev/null @@ -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 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_done -- cgit v0.10.2-6-g49f6 From 0f5a1d449b9538c2765de9d6683abbb83a7fb4e2 Mon Sep 17 00:00:00 2001 From: Denton Liu Date: Sun, 20 Sep 2020 04:22:25 -0700 Subject: 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 Signed-off-by: Junio C Hamano diff --git a/Documentation/git-diff-index.txt b/Documentation/git-diff-index.txt index 25fe165..27acb31 100644 --- a/Documentation/git-diff-index.txt +++ b/Documentation/git-diff-index.txt @@ -9,7 +9,7 @@ git-diff-index - Compare a tree to the working tree or index SYNOPSIS -------- [verse] -'git diff-index' [-m] [--cached] [] [...] +'git diff-index' [-m] [--cached] [--merge-base] [] [...] DESCRIPTION ----------- @@ -29,6 +29,11 @@ include::diff-options.txt[] --cached:: Do not consider the on-disk file at all. +--merge-base:: + Instead of comparing directly, use the merge base + between and HEAD instead. must be a + commit. + -m:: By default, files recorded in the index but not checked out are reported as deleted. This flag makes diff --git a/Documentation/git-diff.txt b/Documentation/git-diff.txt index 8f7b4ed..762ee6d 100644 --- a/Documentation/git-diff.txt +++ b/Documentation/git-diff.txt @@ -10,7 +10,7 @@ SYNOPSIS -------- [verse] 'git diff' [] [] [--] [...] -'git diff' [] --cached [] [--] [...] +'git diff' [] --cached [--merge-base] [] [--] [...] 'git diff' [] [...] [--] [...] 'git diff' [] ... [--] [...] 'git diff' [] @@ -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' [] --cached [] [--] [...]:: +'git diff' [] --cached [--merge-base] [] [--] [...]:: This form is to view the changes you staged for the next commit relative to the named . Typically you @@ -49,6 +49,10 @@ files on disk. If HEAD does not exist (e.g. unborn branches) and is not given, it shows all staged changes. --staged is a synonym of --cached. ++ +If --merge-base is given, instead of using , use the merge base +of and HEAD. `git diff --merge-base A` is equivalent to +`git diff $(git merge-base A HEAD)`. 'git diff' [] [--] [...]:: @@ -89,8 +93,8 @@ files on disk. Just in case you are doing something exotic, it should be noted that all of the 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 `..` +notations, can be any . For a more complete list of ways to spell , see "SPECIFYING REVISIONS" section in linkgit:gitrevisions[7]. diff --git a/builtin/diff-index.c b/builtin/diff-index.c index c3878f7..7f5281c 100644 --- a/builtin/diff-index.c +++ b/builtin/diff-index.c @@ -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); } diff --git a/builtin/diff.c b/builtin/diff.c index e45e19e..1baea18 100644 --- a/builtin/diff.c +++ b/builtin/diff.c @@ -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--; diff --git a/diff-lib.c b/diff-lib.c index 468e3fe..c578560 100644 --- a/diff-lib.c +++ b/diff-lib.c @@ -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 fedfeab..6c2efa1 100644 --- 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 *); diff --git a/t/t4068-diff-symmetric-merge-base.sh b/t/t4068-diff-symmetric-merge-base.sh index bd4cf25..4943237 100755 --- a/t/t4068-diff-symmetric-merge-base.sh +++ b/t/t4068-diff-symmetric-merge-base.sh @@ -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 -- cgit v0.10.2-6-g49f6 From 3d09c22869a7bd47f1683031937af0ed93b2fa3b Mon Sep 17 00:00:00 2001 From: Denton Liu Date: Mon, 14 Sep 2020 11:36:52 -0700 Subject: builtin/diff-tree: learn --merge-base 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 Signed-off-by: Junio C Hamano diff --git a/Documentation/git-diff-tree.txt b/Documentation/git-diff-tree.txt index 5c8a2a5..2fc24c5 100644 --- a/Documentation/git-diff-tree.txt +++ b/Documentation/git-diff-tree.txt @@ -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] [] [] [...] 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 s directly, use the merge + base between the two s as the "before" side. There + must be two s given and they must both be commits. + --stdin:: When `--stdin` is specified, the command does not take arguments from the command line. Instead, it diff --git a/Documentation/git-diff.txt b/Documentation/git-diff.txt index 762ee6d..7f4c8a8 100644 --- a/Documentation/git-diff.txt +++ b/Documentation/git-diff.txt @@ -11,7 +11,7 @@ SYNOPSIS [verse] 'git diff' [] [] [--] [...] 'git diff' [] --cached [--merge-base] [] [--] [...] -'git diff' [] [...] [--] [...] +'git diff' [] [--merge-base] [...] [--] [...] 'git diff' [] ... [--] [...] 'git diff' [] 'git diff' [] --no-index [--] @@ -62,10 +62,14 @@ of and HEAD. `git diff --merge-base A` is equivalent to branch name to compare with the tip of a different branch. -'git diff' [] [--] [...]:: +'git diff' [] [--merge-base] [--] [...]:: This is to view the changes between two arbitrary . ++ +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' [] ... [--] [...]:: diff --git a/builtin/diff-tree.c b/builtin/diff-tree.c index 802363d..9fc95e9 100644 --- a/builtin/diff-tree.c +++ b/builtin/diff-tree.c @@ -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); diff --git a/builtin/diff.c b/builtin/diff.c index 1baea18..b50fc68 100644 --- a/builtin/diff.c +++ b/builtin/diff.c @@ -26,7 +26,7 @@ static const char builtin_diff_usage[] = "git diff [] [] [--] [...]\n" " or: git diff [] --cached [] [--] [...]\n" -" or: git diff [] [...] [--] [...]\n" +" or: git diff [] [--merge-base] [...] [--] [...]\n" " or: git diff [] ...] [--] [...]\n" " or: git diff [] ]\n" " or: git diff [] --no-index [--] ]\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; diff --git a/t/t4068-diff-symmetric-merge-base.sh b/t/t4068-diff-symmetric-merge-base.sh index 4943237..03487cc 100755 --- a/t/t4068-diff-symmetric-merge-base.sh +++ b/t/t4068-diff-symmetric-merge-base.sh @@ -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 -- cgit v0.10.2-6-g49f6 From cce7d6ecfc45f0d74a95c6ca6447d6e327791348 Mon Sep 17 00:00:00 2001 From: Denton Liu Date: Sun, 20 Sep 2020 04:22:27 -0700 Subject: contrib/completion: complete `git diff --merge-base` Signed-off-by: Denton Liu Signed-off-by: Junio C Hamano diff --git a/contrib/completion/git-completion.bash b/contrib/completion/git-completion.bash index f68c8e0..679d1ec 100644 --- a/contrib/completion/git-completion.bash +++ b/contrib/completion/git-completion.bash @@ -1692,7 +1692,7 @@ __git_diff_common_options="--stat --numstat --shortstat --summary " __git_diff_difftool_options="--cached --staged --pickaxe-all --pickaxe-regex - --base --ours --theirs --no-index --relative + --base --ours --theirs --no-index --relative --merge-base $__git_diff_common_options" _git_diff () -- cgit v0.10.2-6-g49f6