diff --git a/Documentation/git-range-diff.adoc b/Documentation/git-range-diff.adoc index 5cc5e2ed56..d448f897d6 100644 --- a/Documentation/git-range-diff.adoc +++ b/Documentation/git-range-diff.adoc @@ -10,7 +10,8 @@ SYNOPSIS [synopsis] git range-diff [--color=[]] [--no-color] [] [--no-dual-color] [--creation-factor=] - [--left-only | --right-only] [--diff-merges=] + [--left-only | --right-only | --matched-only] + [--diff-merges=] [--remerge-diff] [--no-notes | --notes[=]] ( | ... | ) [[--] ...] @@ -82,6 +83,12 @@ to revert to color all lines according to the outer diff markers Suppress commits that are missing from the second specified range (or the "right range" when using the `...` form). +`--matched-only`:: + Only emit commits that have a corresponding commit in the other + range, suppressing any commit that exists on only one side. Useful + to skip added or removed commits when reviewing how the commits + that survived a rebase changed. + `--diff-merges=`:: Instead of ignoring merge commits, generate diffs for them using the corresponding `--diff-merges=` option of linkgit:git-log[1], diff --git a/builtin/range-diff.c b/builtin/range-diff.c index e54c0f7fe1..8059f92eaa 100644 --- a/builtin/range-diff.c +++ b/builtin/range-diff.c @@ -46,7 +46,7 @@ int cmd_range_diff(int argc, .diffopt = &diffopt, .log_arg = &log_arg }; - int simple_color = -1, left_only = 0, right_only = 0; + int simple_color = -1, left_only = 0, right_only = 0, matched_only = 0; struct option range_diff_options[] = { OPT_INTEGER(0, "creation-factor", &range_diff_opts.creation_factor, @@ -68,6 +68,8 @@ int cmd_range_diff(int argc, N_("only emit output related to the first range")), OPT_BOOL(0, "right-only", &right_only, N_("only emit output related to the second range")), + OPT_BOOL(0, "matched-only", &matched_only, + N_("only emit commits that have a corresponding commit in the other range")), OPT_END() }; struct option *options; @@ -186,6 +188,7 @@ int cmd_range_diff(int argc, range_diff_opts.dual_color = simple_color < 1; range_diff_opts.left_only = left_only; range_diff_opts.right_only = right_only; + range_diff_opts.matched_only = matched_only; res = show_range_diff(range1.buf, range2.buf, &range_diff_opts); strvec_clear(&log_arg); diff --git a/range-diff.c b/range-diff.c index 8e2dd2eb19..fa895f5760 100644 --- a/range-diff.c +++ b/range-diff.c @@ -591,8 +591,15 @@ int show_range_diff(const char *range1, const char *range2, struct string_list branch2 = STRING_LIST_INIT_DUP; unsigned int include_merges = range_diff_opts->include_merges; - if (range_diff_opts->left_only && range_diff_opts->right_only) - res = error(_("options '%s' and '%s' cannot be used together"), "--left-only", "--right-only"); + if (range_diff_opts->left_only + range_diff_opts->right_only + + range_diff_opts->matched_only > 1) + res = error(_("options '%s', '%s', or '%s' cannot be used together"), + "--left-only", "--right-only", "--matched-only"); + + if (range_diff_opts->matched_only) { + range_diff_opts->left_only = 1; + range_diff_opts->right_only = 1; + } if (!res && read_patches(range1, &branch1, range_diff_opts->log_arg, include_merges)) res = error(_("could not parse log for '%s'"), range1); diff --git a/range-diff.h b/range-diff.h index 9b70a80009..effd10b9b8 100644 --- a/range-diff.h +++ b/range-diff.h @@ -19,7 +19,7 @@ struct range_diff_options { int creation_factor; unsigned dual_color:1; - unsigned left_only:1, right_only:1; + unsigned left_only:1, right_only:1, matched_only:1; unsigned include_merges:1; size_t max_memory; const struct diff_options *diffopt; /* may be NULL */ diff --git a/t/t3206-range-diff.sh b/t/t3206-range-diff.sh index ef92704de3..f85fd0c4ad 100755 --- a/t/t3206-range-diff.sh +++ b/t/t3206-range-diff.sh @@ -860,6 +860,69 @@ test_expect_success '--left-only/--right-only' ' test_cmp expect actual ' +test_expect_success '--left-only, --right-only and --matched-only are incompatible' ' + test_must_fail git range-diff --left-only --right-only ...common 2>err && + test_grep "cannot be used together" err && + + test_must_fail git range-diff --left-only --matched-only ...common 2>err && + test_grep "cannot be used together" err && + + test_must_fail git range-diff --right-only --matched-only ...common 2>err && + test_grep "cannot be used together" err && + + test_must_fail git range-diff --left-only --right-only --matched-only \ + ...common 2>err && + test_grep "cannot be used together" err +' + +test_expect_success '--left-only, --right-only and --matched-only each suppress one-sided commits' ' + test_create_repo matched-only && + ( + cd matched-only && + git switch --orphan combined-old && + test_commit c-first && + test_commit c-old-only && + test_commit c-common && + git switch -C combined-new c-first && + test_commit c-new-only && + git cherry-pick c-common && + + old_only_oid=$(git rev-parse --short=7 c-old-only) && + new_only_oid=$(git rev-parse --short=7 c-new-only) && + common_old_oid=$(git rev-parse --short=7 c-common) && + common_new_oid=$(git rev-parse --short=7 HEAD) && + + git range-diff -s --abbrev=7 combined-old...combined-new >actual && + cat >expect <<-EOF && + 1: $old_only_oid < -: ------- c-old-only + -: ------- > 1: $new_only_oid c-new-only + 2: $common_old_oid = 2: $common_new_oid c-common + EOF + test_cmp expect actual && + + git range-diff -s --abbrev=7 --left-only combined-old...combined-new \ + >actual && + cat >expect <<-EOF && + 1: $old_only_oid < -: ------- c-old-only + 2: $common_old_oid = 2: $common_new_oid c-common + EOF + test_cmp expect actual && + + git range-diff -s --abbrev=7 --right-only combined-old...combined-new \ + >actual && + cat >expect <<-EOF && + -: ------- > 1: $new_only_oid c-new-only + 2: $common_old_oid = 2: $common_new_oid c-common + EOF + test_cmp expect actual && + + git range-diff -s --abbrev=7 --matched-only combined-old...combined-new \ + >actual && + echo "2: $common_old_oid = 2: $common_new_oid c-common" >expect && + test_cmp expect actual + ) +' + test_expect_success 'ranges with pathspecs' ' git range-diff topic...mode-only-change -- other-file >actual && test_line_count = 2 actual &&