{"thread":{"id":"66310","subject":"[PATCH] range-diff: add --matched-only to skip one-sided commits","startedAt":"2026-09-11T16:41:25Z","lastAt":"2026-09-15T07:51:45Z","messageCount":9,"participants":["Harald Nordgren via GitGitGadget","Junio C Hamano","Harald Nordgren"],"isPatch":true,"patchVersion":1,"patchTotal":null},"messages":[{"id":"552569","messageId":"pull.2401.git.git.1789144877632.gitgitgadget@gmail.com","threadId":"66310","inReplyTo":null,"subject":"[PATCH] range-diff: add --matched-only to skip one-sided commits","fromName":"Harald Nordgren via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2026-09-11T16:41:17Z","receivedAt":"2026-09-11T16:41:25Z","isPatch":true,"body":"From: Harald Nordgren <haraldnordgren@gmail.com>\n\nReviewing a range-diff often means scrolling past commits that were\nsimply added or dropped, when only the ones that correspond between\nthe two ranges are of interest.\n\n--left-only and --right-only already each suppress one of those\none-sided groups, so give --matched-only its own name for applying\nboth suppressions at once instead of documenting the combination of\ntwo options whose names read as contradictory together. Internally it\njust sets both flags, reusing the existing suppression logic in\nshow_range_diff().\n\nExtend the existing '--left-only'/'--right-only' conflict check in\nshow_range_diff() to also reject any combination with --matched-only,\nsince all three narrow the output in ways that cannot be combined.\n\nSigned-off-by: Harald Nordgren <haraldnordgren@gmail.com>\n---\n    range-diff: add --matched-only to skip one-sided commits\n    \n    Add git range-diff --matched-only to only show commits that correspond\n    between the two ranges, skipping ones that were only added or only\n    removed.\n\nPublished-As: https://github.com/gitgitgadget/git/releases/tag/pr-git-2401%2FHaraldNordgren%2Frange-diff-matched-only-v1\nFetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-git-2401/HaraldNordgren/range-diff-matched-only-v1\nPull-Request: https://github.com/git/git/pull/2401\n\n Documentation/git-range-diff.adoc | 10 ++++-\n builtin/range-diff.c              |  5 ++-\n range-diff.c                      | 11 +++++-\n range-diff.h                      |  2 +-\n t/t3206-range-diff.sh             | 63 +++++++++++++++++++++++++++++++\n 5 files changed, 86 insertions(+), 5 deletions(-)\n\ndiff --git a/Documentation/git-range-diff.adoc b/Documentation/git-range-diff.adoc\nindex 5cc5e2ed56..58e59e8e3b 100644\n--- a/Documentation/git-range-diff.adoc\n+++ b/Documentation/git-range-diff.adoc\n@@ -10,7 +10,8 @@ SYNOPSIS\n [synopsis]\n git range-diff [--color=[<when>]] [--no-color] [<diff-options>]\n \t[--no-dual-color] [--creation-factor=<factor>]\n-\t[--left-only | --right-only] [--diff-merges=<format>]\n+\t[--left-only | --right-only | --matched-only]\n+\t[--diff-merges=<format>]\n \t[--remerge-diff] [--no-notes | --notes[=<ref>]]\n \t( <range1> <range2> | <rev1>...<rev2> | <base> <rev1> <rev2> )\n \t[[--] <path>...]\n@@ -82,6 +83,13 @@ to revert to color all lines according to the outer diff markers\n \tSuppress commits that are missing from the second specified range\n \t(or the \"right range\" when using the `<rev1>...<rev2>` form).\n \n+`--matched-only`::\n+\tOnly emit commits that have a corresponding commit in the other\n+\trange, suppressing any commit that exists on only one side. This is\n+\tthe same as using `--left-only` and `--right-only` together. Useful\n+\tto skip added or removed commits when reviewing how the commits\n+\tthat survived a rebase changed.\n+\n `--diff-merges=<format>`::\n \tInstead of ignoring merge commits, generate diffs for them using the\n \tcorresponding `--diff-merges=<format>` option of linkgit:git-log[1],\ndiff --git a/builtin/range-diff.c b/builtin/range-diff.c\nindex e54c0f7fe1..8059f92eaa 100644\n--- a/builtin/range-diff.c\n+++ b/builtin/range-diff.c\n@@ -46,7 +46,7 @@ int cmd_range_diff(int argc,\n \t\t.diffopt = &diffopt,\n \t\t.log_arg = &log_arg\n \t};\n-\tint simple_color = -1, left_only = 0, right_only = 0;\n+\tint simple_color = -1, left_only = 0, right_only = 0, matched_only = 0;\n \tstruct option range_diff_options[] = {\n \t\tOPT_INTEGER(0, \"creation-factor\",\n \t\t\t    &range_diff_opts.creation_factor,\n@@ -68,6 +68,8 @@ int cmd_range_diff(int argc,\n \t\t\t N_(\"only emit output related to the first range\")),\n \t\tOPT_BOOL(0, \"right-only\", &right_only,\n \t\t\t N_(\"only emit output related to the second range\")),\n+\t\tOPT_BOOL(0, \"matched-only\", &matched_only,\n+\t\t\t N_(\"only emit commits that have a corresponding commit in the other range\")),\n \t\tOPT_END()\n \t};\n \tstruct option *options;\n@@ -186,6 +188,7 @@ int cmd_range_diff(int argc,\n \trange_diff_opts.dual_color = simple_color < 1;\n \trange_diff_opts.left_only = left_only;\n \trange_diff_opts.right_only = right_only;\n+\trange_diff_opts.matched_only = matched_only;\n \tres = show_range_diff(range1.buf, range2.buf, &range_diff_opts);\n \n \tstrvec_clear(&log_arg);\ndiff --git a/range-diff.c b/range-diff.c\nindex 8e2dd2eb19..fa895f5760 100644\n--- a/range-diff.c\n+++ b/range-diff.c\n@@ -591,8 +591,15 @@ int show_range_diff(const char *range1, const char *range2,\n \tstruct string_list branch2 = STRING_LIST_INIT_DUP;\n \tunsigned int include_merges = range_diff_opts->include_merges;\n \n-\tif (range_diff_opts->left_only && range_diff_opts->right_only)\n-\t\tres = error(_(\"options '%s' and '%s' cannot be used together\"), \"--left-only\", \"--right-only\");\n+\tif (range_diff_opts->left_only + range_diff_opts->right_only +\n+\t    range_diff_opts->matched_only > 1)\n+\t\tres = error(_(\"options '%s', '%s', or '%s' cannot be used together\"),\n+\t\t\t    \"--left-only\", \"--right-only\", \"--matched-only\");\n+\n+\tif (range_diff_opts->matched_only) {\n+\t\trange_diff_opts->left_only = 1;\n+\t\trange_diff_opts->right_only = 1;\n+\t}\n \n \tif (!res && read_patches(range1, &branch1, range_diff_opts->log_arg, include_merges))\n \t\tres = error(_(\"could not parse log for '%s'\"), range1);\ndiff --git a/range-diff.h b/range-diff.h\nindex 9b70a80009..effd10b9b8 100644\n--- a/range-diff.h\n+++ b/range-diff.h\n@@ -19,7 +19,7 @@\n struct range_diff_options {\n \tint creation_factor;\n \tunsigned dual_color:1;\n-\tunsigned left_only:1, right_only:1;\n+\tunsigned left_only:1, right_only:1, matched_only:1;\n \tunsigned include_merges:1;\n \tsize_t max_memory;\n \tconst struct diff_options *diffopt; /* may be NULL */\ndiff --git a/t/t3206-range-diff.sh b/t/t3206-range-diff.sh\nindex ef92704de3..f85fd0c4ad 100755\n--- a/t/t3206-range-diff.sh\n+++ b/t/t3206-range-diff.sh\n@@ -860,6 +860,69 @@ test_expect_success '--left-only/--right-only' '\n \ttest_cmp expect actual\n '\n \n+test_expect_success '--left-only, --right-only and --matched-only are incompatible' '\n+\ttest_must_fail git range-diff --left-only --right-only ...common 2>err &&\n+\ttest_grep \"cannot be used together\" err &&\n+\n+\ttest_must_fail git range-diff --left-only --matched-only ...common 2>err &&\n+\ttest_grep \"cannot be used together\" err &&\n+\n+\ttest_must_fail git range-diff --right-only --matched-only ...common 2>err &&\n+\ttest_grep \"cannot be used together\" err &&\n+\n+\ttest_must_fail git range-diff --left-only --right-only --matched-only \\\n+\t\t...common 2>err &&\n+\ttest_grep \"cannot be used together\" err\n+'\n+\n+test_expect_success '--left-only, --right-only and --matched-only each suppress one-sided commits' '\n+\ttest_create_repo matched-only &&\n+\t(\n+\t\tcd matched-only &&\n+\t\tgit switch --orphan combined-old &&\n+\t\ttest_commit c-first &&\n+\t\ttest_commit c-old-only &&\n+\t\ttest_commit c-common &&\n+\t\tgit switch -C combined-new c-first &&\n+\t\ttest_commit c-new-only &&\n+\t\tgit cherry-pick c-common &&\n+\n+\t\told_only_oid=$(git rev-parse --short=7 c-old-only) &&\n+\t\tnew_only_oid=$(git rev-parse --short=7 c-new-only) &&\n+\t\tcommon_old_oid=$(git rev-parse --short=7 c-common) &&\n+\t\tcommon_new_oid=$(git rev-parse --short=7 HEAD) &&\n+\n+\t\tgit range-diff -s --abbrev=7 combined-old...combined-new >actual &&\n+\t\tcat >expect <<-EOF &&\n+\t\t1:  $old_only_oid < -:  ------- c-old-only\n+\t\t-:  ------- > 1:  $new_only_oid c-new-only\n+\t\t2:  $common_old_oid = 2:  $common_new_oid c-common\n+\t\tEOF\n+\t\ttest_cmp expect actual &&\n+\n+\t\tgit range-diff -s --abbrev=7 --left-only combined-old...combined-new \\\n+\t\t\t>actual &&\n+\t\tcat >expect <<-EOF &&\n+\t\t1:  $old_only_oid < -:  ------- c-old-only\n+\t\t2:  $common_old_oid = 2:  $common_new_oid c-common\n+\t\tEOF\n+\t\ttest_cmp expect actual &&\n+\n+\t\tgit range-diff -s --abbrev=7 --right-only combined-old...combined-new \\\n+\t\t\t>actual &&\n+\t\tcat >expect <<-EOF &&\n+\t\t-:  ------- > 1:  $new_only_oid c-new-only\n+\t\t2:  $common_old_oid = 2:  $common_new_oid c-common\n+\t\tEOF\n+\t\ttest_cmp expect actual &&\n+\n+\t\tgit range-diff -s --abbrev=7 --matched-only combined-old...combined-new \\\n+\t\t\t>actual &&\n+\t\techo \"2:  $common_old_oid = 2:  $common_new_oid c-common\" >expect &&\n+\t\ttest_cmp expect actual\n+\t)\n+'\n+\n test_expect_success 'ranges with pathspecs' '\n \tgit range-diff topic...mode-only-change -- other-file >actual &&\n \ttest_line_count = 2 actual &&\n\nbase-commit: fa7f9290efe2bd22dd736689597b474b93798e11\n-- \ngitgitgadget\n"},{"id":"552575","messageId":"xmqqik4bbt9c.fsf@gitster.g","threadId":"66310","inReplyTo":"pull.2401.git.git.1789144877632.gitgitgadget@gmail.com","subject":"Re: [PATCH] range-diff: add --matched-only to skip one-sided commits","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2026-09-11T17:19:27Z","receivedAt":"2026-09-11T17:19:30Z","isPatch":true,"body":"\"Harald Nordgren via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n\n> diff --git a/Documentation/git-range-diff.adoc b/Documentation/git-range-diff.adoc\n> index 5cc5e2ed56..58e59e8e3b 100644\n> --- a/Documentation/git-range-diff.adoc\n> +++ b/Documentation/git-range-diff.adoc\n> @@ -10,7 +10,8 @@ SYNOPSIS\n>  [synopsis]\n>  git range-diff [--color=[<when>]] [--no-color] [<diff-options>]\n>  \t[--no-dual-color] [--creation-factor=<factor>]\n> -\t[--left-only | --right-only] [--diff-merges=<format>]\n> +\t[--left-only | --right-only | --matched-only]\n> +\t[--diff-merges=<format>]\n>  \t[--remerge-diff] [--no-notes | --notes[=<ref>]]\n>  \t( <range1> <range2> | <rev1>...<rev2> | <base> <rev1> <rev2> )\n>  \t[[--] <path>...]\n> @@ -82,6 +83,13 @@ to revert to color all lines according to the outer diff markers\n>  \tSuppress commits that are missing from the second specified range\n>  \t(or the \"right range\" when using the `<rev1>...<rev2>` form).\n>  \n> +`--matched-only`::\n> +\tOnly emit commits that have a corresponding commit in the other\n> +\trange, suppressing any commit that exists on only one side. This is\n> +\tthe same as using `--left-only` and `--right-only` together. Useful\n> +\tto skip added or removed commits when reviewing how the commits\n> +\tthat survived a rebase changed.\n\nWhile conceptually it is the same as giving \"--hide-right-only\"\n(which would have hidden the right-only entry) and\n\"--hide-left-only\" at the same time, because the existing two\noptions are not defined in terms of \"hiding\" entries that have only\none side (which would have logically allowed combining) but instead\nshowing \"only\" one side (which makes it impossible to give them\ntogether, and indeed that is the first thing\nrange-diff.c:show_range_diff() checks and yields an error), this\ndescription is not accurate.\n\nI wonder if the implementation actually can be more like\n\n - give \"--hide-left-only\" and \"--hide-right-only\" as synonyms to\n   \"--right-only\" and \"--left-only\", and deprecate the original;\n\n - allow them to be given together, which will give the new\n   behaviour you are introducing, i.e., skip steps without both\n   sides from the output;\n\n - give a short-hand synonym, \"--matched-only\", to truly behave the\n   same as giving \"--hide-{left,right}-only\" together.\n\nwhich would allow the above explanation to be more accurate?  I\ndunno.\n\n> +\tif (range_diff_opts->left_only + range_diff_opts->right_only +\n> +\t    range_diff_opts->matched_only > 1)\n> +\t\tres = error(_(\"options '%s', '%s', or '%s' cannot be used together\"),\n> +\t\t\t    \"--left-only\", \"--right-only\", \"--matched-only\");\n\nDon't we have die_for_incompatible_opt3() to do this?\n\nThe basic idea sounds good.  The unmatched entries do serve as a\nstrong hint that a greater --creation-factor may help.  For example,\n\n> +\t\tgit range-diff -s --abbrev=7 combined-old...combined-new >actual &&\n> +\t\tcat >expect <<-EOF &&\n> +\t\t1:  $old_only_oid < -:  ------- c-old-only\n> +\t\t-:  ------- > 1:  $new_only_oid c-new-only\n> +\t\t2:  $common_old_oid = 2:  $common_new_oid c-common\n> +\t\tEOF\n> +\t\ttest_cmp expect actual &&\n\nthe above clearly shows that the command might compare c-old-only\nand c-new-only with a better creation factor settings.\n\nBut because the entries are numbered, gaps in the numbers, like this\noutput\n\n> +\t\tgit range-diff -s --abbrev=7 --matched-only combined-old...combined-new \\\n> +\t\t\t>actual &&\n> +\t\techo \"2:  $common_old_oid = 2:  $common_new_oid c-common\" >expect &&\n> +\t\ttest_cmp expect actual\n\nmay be sufficient (we can tell that 1 was omitted), except that\nsomehow we at least need to be aware that there were only 2 commits\non both sides (it may be hiding commits 3 thru 99 as unmatching\npairs and we lose that hint from the new output), which is not a\nhuge downside.\n\nThanks.\n"},{"id":"552582","messageId":"CAHwyqnWkEaQHsp5GtYGL9qg-FYA0Ngt_+omdh8jmw4KfqfX6Uw@mail.gmail.com","threadId":"66310","inReplyTo":"xmqqik4bbt9c.fsf@gitster.g","subject":"Re: [PATCH] range-diff: add --matched-only to skip one-sided commits","fromName":"Harald Nordgren","fromEmail":"haraldnordgren@gmail.com","sentAt":"2026-09-11T18:48:45Z","receivedAt":"2026-09-11T18:49:25Z","isPatch":true,"body":"> I wonder if the implementation actually can be more like\n>\n>  - give \"--hide-left-only\" and \"--hide-right-only\" as synonyms to\n>    \"--right-only\" and \"--left-only\", and deprecate the original;\n>\n>  - allow them to be given together, which will give the new\n>    behaviour you are introducing, i.e., skip steps without both\n>    sides from the output;\n>\n>  - give a short-hand synonym, \"--matched-only\", to truly behave the\n>    same as giving \"--hide-{left,right}-only\" together.\n\nSeems like a big change, and deprecated options are a pain in the neck\nbecause we can never actually remove them.\n\nIf we decide to go this way, we might name them \"--hide-{left,right}\"\nand just not introduce a condition that makes them incompatible. Then\n\"--matched-only\" would be pure syntactic sugar and wouldn't even be\n100% necessary to have to achieve this.\n\n\nHarald\n"},{"id":"552583","messageId":"xmqqse3faadj.fsf@gitster.g","threadId":"66310","inReplyTo":"CAHwyqnWkEaQHsp5GtYGL9qg-FYA0Ngt_+omdh8jmw4KfqfX6Uw@mail.gmail.com","subject":"Re: [PATCH] range-diff: add --matched-only to skip one-sided commits","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2026-09-11T18:52:40Z","receivedAt":"2026-09-11T18:52:43Z","isPatch":true,"body":"Harald Nordgren <haraldnordgren@gmail.com> writes:\n\n>> I wonder if the implementation actually can be more like\n>>\n>>  - give \"--hide-left-only\" and \"--hide-right-only\" as synonyms to\n>>    \"--right-only\" and \"--left-only\", and deprecate the original;\n>>\n>>  - allow them to be given together, which will give the new\n>>    behaviour you are introducing, i.e., skip steps without both\n>>    sides from the output;\n>>\n>>  - give a short-hand synonym, \"--matched-only\", to truly behave the\n>>    same as giving \"--hide-{left,right}-only\" together.\n>\n> Seems like a big change, and deprecated options are a pain in the neck\n> because we can never actually remove them.\n>\n> If we decide to go this way, we might name them \"--hide-{left,right}\"\n> and just not introduce a condition that makes them incompatible. Then\n> \"--matched-only\" would be pure syntactic sugar and wouldn't even be\n> 100% necessary to have to achieve this.\n\nOr we can just keep the code and fix the documentation.  I think\nthat would be much less impact.\n\n"},{"id":"552585","messageId":"CAHwyqnWVXuDMAonj1jK9687xhjiT4h-0E2qzhjwPXOTKfMhoGQ@mail.gmail.com","threadId":"66310","inReplyTo":"xmqqse3faadj.fsf@gitster.g","subject":"Re: [PATCH] range-diff: add --matched-only to skip one-sided commits","fromName":"Harald Nordgren","fromEmail":"haraldnordgren@gmail.com","sentAt":"2026-09-11T19:01:24Z","receivedAt":"2026-09-11T19:02:09Z","isPatch":true,"body":"> > Seems like a big change, and deprecated options are a pain in the neck\n> > because we can never actually remove them.\n> >\n> > If we decide to go this way, we might name them \"--hide-{left,right}\"\n> > and just not introduce a condition that makes them incompatible. Then\n> > \"--matched-only\" would be pure syntactic sugar and wouldn't even be\n> > 100% necessary to have to achieve this.\n>\n> Or we can just keep the code and fix the documentation.  I think\n> that would be much less impact.\n\nI agree.\n\n\nHarald\n"},{"id":"552586","messageId":"xmqqik4ba9sq.fsf@gitster.g","threadId":"66310","inReplyTo":"CAHwyqnWVXuDMAonj1jK9687xhjiT4h-0E2qzhjwPXOTKfMhoGQ@mail.gmail.com","subject":"Re: [PATCH] range-diff: add --matched-only to skip one-sided commits","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2026-09-11T19:05:09Z","receivedAt":"2026-09-11T19:05:14Z","isPatch":true,"body":"Harald Nordgren <haraldnordgren@gmail.com> writes:\n\n>> > Seems like a big change, and deprecated options are a pain in the neck\n>> > because we can never actually remove them.\n>> >\n>> > If we decide to go this way, we might name them \"--hide-{left,right}\"\n>> > and just not introduce a condition that makes them incompatible. Then\n>> > \"--matched-only\" would be pure syntactic sugar and wouldn't even be\n>> > 100% necessary to have to achieve this.\n>>\n>> Or we can just keep the code and fix the documentation.  I think\n>> that would be much less impact.\n>\n> I agree.\n>\n>\n> Harald\n\nI thought I'd try my own version, but it seems that we can simply\nremove the misleading sentence and the remainder already is very\neasy to read and understand ;-)\n\n`--matched-only`::\n\tOnly emit commits that have a corresponding commit in the other\n\trange, suppressing any commit that exists on only one side.  Useful\n\tto skip added or removed commits when reviewing how the commits\n\tthat survived a rebase changed.\n\n"},{"id":"552594","messageId":"pull.2401.v2.git.git.1789160138305.gitgitgadget@gmail.com","threadId":"66310","inReplyTo":"pull.2401.git.git.1789144877632.gitgitgadget@gmail.com","subject":"[PATCH v2] range-diff: add --matched-only to skip one-sided commits","fromName":"Harald Nordgren via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2026-09-11T20:55:38Z","receivedAt":"2026-09-11T20:55:41Z","isPatch":true,"body":"From: Harald Nordgren <haraldnordgren@gmail.com>\n\nReviewing a range-diff often means scrolling past commits that were\nsimply added or dropped, when only the ones that correspond between\nthe two ranges are of interest.\n\n--left-only and --right-only already each suppress one of those\none-sided groups, but they are defined as \"only show this side\" and\nso cannot be given together, which is exactly why show_range_diff()\nalready rejected that combination. Give the \"show only the commits\nthat correspond on both sides\" behavior its own name, --matched-only,\ninstead of asking users to reach for a combination that errors out.\n\nExtend the existing '--left-only'/'--right-only' conflict check to\nalso reject any combination with --matched-only, since all three\nnarrow the output in ways that cannot be combined.\n\nSigned-off-by: Harald Nordgren <haraldnordgren@gmail.com>\n---\n    range-diff: add --matched-only to skip one-sided commits\n    \n    Add git range-diff --matched-only to only show commits that correspond\n    between the two ranges, skipping ones that were only added or only\n    removed.\n    \n    Changes in v2:\n    \n     * Update docs and commit message.\n     * Use die_for_incompatible_opt3.\n\nPublished-As: https://github.com/gitgitgadget/git/releases/tag/pr-git-2401%2FHaraldNordgren%2Frange-diff-matched-only-v2\nFetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-git-2401/HaraldNordgren/range-diff-matched-only-v2\nPull-Request: https://github.com/git/git/pull/2401\n\nRange-diff vs v1:\n\n 1:  edb4471088 ! 1:  6d392249a2 range-diff: add --matched-only to skip one-sided commits\n     @@ Commit message\n          the two ranges are of interest.\n      \n          --left-only and --right-only already each suppress one of those\n     -    one-sided groups, so give --matched-only its own name for applying\n     -    both suppressions at once instead of documenting the combination of\n     -    two options whose names read as contradictory together. Internally it\n     -    just sets both flags, reusing the existing suppression logic in\n     -    show_range_diff().\n     +    one-sided groups, but they are defined as \"only show this side\" and\n     +    so cannot be given together, which is exactly why show_range_diff()\n     +    already rejected that combination. Give the \"show only the commits\n     +    that correspond on both sides\" behavior its own name, --matched-only,\n     +    instead of asking users to reach for a combination that errors out.\n      \n     -    Extend the existing '--left-only'/'--right-only' conflict check in\n     -    show_range_diff() to also reject any combination with --matched-only,\n     -    since all three narrow the output in ways that cannot be combined.\n     +    Extend the existing '--left-only'/'--right-only' conflict check to\n     +    also reject any combination with --matched-only, since all three\n     +    narrow the output in ways that cannot be combined.\n      \n          Signed-off-by: Harald Nordgren <haraldnordgren@gmail.com>\n      \n     @@ Documentation/git-range-diff.adoc: to revert to color all lines according to the\n       \n      +`--matched-only`::\n      +\tOnly emit commits that have a corresponding commit in the other\n     -+\trange, suppressing any commit that exists on only one side. This is\n     -+\tthe same as using `--left-only` and `--right-only` together. Useful\n     ++\trange, suppressing any commit that exists on only one side. Useful\n      +\tto skip added or removed commits when reviewing how the commits\n      +\tthat survived a rebase changed.\n      +\n     @@ builtin/range-diff.c: int cmd_range_diff(int argc,\n       \tstrvec_clear(&log_arg);\n      \n       ## range-diff.c ##\n     +@@\n     + #include \"userdiff.h\"\n     + #include \"apply.h\"\n     + #include \"revision.h\"\n     ++#include \"parse-options.h\"\n     + \n     + struct patch_util {\n     + \t/* For the search for an exact match */\n      @@ range-diff.c: int show_range_diff(const char *range1, const char *range2,\n       \tstruct string_list branch2 = STRING_LIST_INIT_DUP;\n       \tunsigned int include_merges = range_diff_opts->include_merges;\n       \n      -\tif (range_diff_opts->left_only && range_diff_opts->right_only)\n      -\t\tres = error(_(\"options '%s' and '%s' cannot be used together\"), \"--left-only\", \"--right-only\");\n     -+\tif (range_diff_opts->left_only + range_diff_opts->right_only +\n     -+\t    range_diff_opts->matched_only > 1)\n     -+\t\tres = error(_(\"options '%s', '%s', or '%s' cannot be used together\"),\n     -+\t\t\t    \"--left-only\", \"--right-only\", \"--matched-only\");\n     -+\n     ++\tdie_for_incompatible_opt3(range_diff_opts->left_only, \"--left-only\",\n     ++\t\t\t\t  range_diff_opts->right_only, \"--right-only\",\n     ++\t\t\t\t  range_diff_opts->matched_only, \"--matched-only\");\n     + \n     +-\tif (!res && read_patches(range1, &branch1, range_diff_opts->log_arg, include_merges))\n      +\tif (range_diff_opts->matched_only) {\n      +\t\trange_diff_opts->left_only = 1;\n      +\t\trange_diff_opts->right_only = 1;\n      +\t}\n     - \n     - \tif (!res && read_patches(range1, &branch1, range_diff_opts->log_arg, include_merges))\n     ++\n     ++\tif (read_patches(range1, &branch1, range_diff_opts->log_arg, include_merges))\n       \t\tres = error(_(\"could not parse log for '%s'\"), range1);\n     + \tif (!res && read_patches(range2, &branch2, range_diff_opts->log_arg, include_merges))\n     + \t\tres = error(_(\"could not parse log for '%s'\"), range2);\n      \n       ## range-diff.h ##\n      @@\n\n\n Documentation/git-range-diff.adoc |  9 ++++-\n builtin/range-diff.c              |  5 ++-\n range-diff.c                      | 13 +++++--\n range-diff.h                      |  2 +-\n t/t3206-range-diff.sh             | 63 +++++++++++++++++++++++++++++++\n 5 files changed, 86 insertions(+), 6 deletions(-)\n\ndiff --git a/Documentation/git-range-diff.adoc b/Documentation/git-range-diff.adoc\nindex 5cc5e2ed56..d448f897d6 100644\n--- a/Documentation/git-range-diff.adoc\n+++ b/Documentation/git-range-diff.adoc\n@@ -10,7 +10,8 @@ SYNOPSIS\n [synopsis]\n git range-diff [--color=[<when>]] [--no-color] [<diff-options>]\n \t[--no-dual-color] [--creation-factor=<factor>]\n-\t[--left-only | --right-only] [--diff-merges=<format>]\n+\t[--left-only | --right-only | --matched-only]\n+\t[--diff-merges=<format>]\n \t[--remerge-diff] [--no-notes | --notes[=<ref>]]\n \t( <range1> <range2> | <rev1>...<rev2> | <base> <rev1> <rev2> )\n \t[[--] <path>...]\n@@ -82,6 +83,12 @@ to revert to color all lines according to the outer diff markers\n \tSuppress commits that are missing from the second specified range\n \t(or the \"right range\" when using the `<rev1>...<rev2>` form).\n \n+`--matched-only`::\n+\tOnly emit commits that have a corresponding commit in the other\n+\trange, suppressing any commit that exists on only one side. Useful\n+\tto skip added or removed commits when reviewing how the commits\n+\tthat survived a rebase changed.\n+\n `--diff-merges=<format>`::\n \tInstead of ignoring merge commits, generate diffs for them using the\n \tcorresponding `--diff-merges=<format>` option of linkgit:git-log[1],\ndiff --git a/builtin/range-diff.c b/builtin/range-diff.c\nindex e54c0f7fe1..8059f92eaa 100644\n--- a/builtin/range-diff.c\n+++ b/builtin/range-diff.c\n@@ -46,7 +46,7 @@ int cmd_range_diff(int argc,\n \t\t.diffopt = &diffopt,\n \t\t.log_arg = &log_arg\n \t};\n-\tint simple_color = -1, left_only = 0, right_only = 0;\n+\tint simple_color = -1, left_only = 0, right_only = 0, matched_only = 0;\n \tstruct option range_diff_options[] = {\n \t\tOPT_INTEGER(0, \"creation-factor\",\n \t\t\t    &range_diff_opts.creation_factor,\n@@ -68,6 +68,8 @@ int cmd_range_diff(int argc,\n \t\t\t N_(\"only emit output related to the first range\")),\n \t\tOPT_BOOL(0, \"right-only\", &right_only,\n \t\t\t N_(\"only emit output related to the second range\")),\n+\t\tOPT_BOOL(0, \"matched-only\", &matched_only,\n+\t\t\t N_(\"only emit commits that have a corresponding commit in the other range\")),\n \t\tOPT_END()\n \t};\n \tstruct option *options;\n@@ -186,6 +188,7 @@ int cmd_range_diff(int argc,\n \trange_diff_opts.dual_color = simple_color < 1;\n \trange_diff_opts.left_only = left_only;\n \trange_diff_opts.right_only = right_only;\n+\trange_diff_opts.matched_only = matched_only;\n \tres = show_range_diff(range1.buf, range2.buf, &range_diff_opts);\n \n \tstrvec_clear(&log_arg);\ndiff --git a/range-diff.c b/range-diff.c\nindex 8e2dd2eb19..67cc751658 100644\n--- a/range-diff.c\n+++ b/range-diff.c\n@@ -20,6 +20,7 @@\n #include \"userdiff.h\"\n #include \"apply.h\"\n #include \"revision.h\"\n+#include \"parse-options.h\"\n \n struct patch_util {\n \t/* For the search for an exact match */\n@@ -591,10 +592,16 @@ int show_range_diff(const char *range1, const char *range2,\n \tstruct string_list branch2 = STRING_LIST_INIT_DUP;\n \tunsigned int include_merges = range_diff_opts->include_merges;\n \n-\tif (range_diff_opts->left_only && range_diff_opts->right_only)\n-\t\tres = error(_(\"options '%s' and '%s' cannot be used together\"), \"--left-only\", \"--right-only\");\n+\tdie_for_incompatible_opt3(range_diff_opts->left_only, \"--left-only\",\n+\t\t\t\t  range_diff_opts->right_only, \"--right-only\",\n+\t\t\t\t  range_diff_opts->matched_only, \"--matched-only\");\n \n-\tif (!res && read_patches(range1, &branch1, range_diff_opts->log_arg, include_merges))\n+\tif (range_diff_opts->matched_only) {\n+\t\trange_diff_opts->left_only = 1;\n+\t\trange_diff_opts->right_only = 1;\n+\t}\n+\n+\tif (read_patches(range1, &branch1, range_diff_opts->log_arg, include_merges))\n \t\tres = error(_(\"could not parse log for '%s'\"), range1);\n \tif (!res && read_patches(range2, &branch2, range_diff_opts->log_arg, include_merges))\n \t\tres = error(_(\"could not parse log for '%s'\"), range2);\ndiff --git a/range-diff.h b/range-diff.h\nindex 9b70a80009..effd10b9b8 100644\n--- a/range-diff.h\n+++ b/range-diff.h\n@@ -19,7 +19,7 @@\n struct range_diff_options {\n \tint creation_factor;\n \tunsigned dual_color:1;\n-\tunsigned left_only:1, right_only:1;\n+\tunsigned left_only:1, right_only:1, matched_only:1;\n \tunsigned include_merges:1;\n \tsize_t max_memory;\n \tconst struct diff_options *diffopt; /* may be NULL */\ndiff --git a/t/t3206-range-diff.sh b/t/t3206-range-diff.sh\nindex ef92704de3..f85fd0c4ad 100755\n--- a/t/t3206-range-diff.sh\n+++ b/t/t3206-range-diff.sh\n@@ -860,6 +860,69 @@ test_expect_success '--left-only/--right-only' '\n \ttest_cmp expect actual\n '\n \n+test_expect_success '--left-only, --right-only and --matched-only are incompatible' '\n+\ttest_must_fail git range-diff --left-only --right-only ...common 2>err &&\n+\ttest_grep \"cannot be used together\" err &&\n+\n+\ttest_must_fail git range-diff --left-only --matched-only ...common 2>err &&\n+\ttest_grep \"cannot be used together\" err &&\n+\n+\ttest_must_fail git range-diff --right-only --matched-only ...common 2>err &&\n+\ttest_grep \"cannot be used together\" err &&\n+\n+\ttest_must_fail git range-diff --left-only --right-only --matched-only \\\n+\t\t...common 2>err &&\n+\ttest_grep \"cannot be used together\" err\n+'\n+\n+test_expect_success '--left-only, --right-only and --matched-only each suppress one-sided commits' '\n+\ttest_create_repo matched-only &&\n+\t(\n+\t\tcd matched-only &&\n+\t\tgit switch --orphan combined-old &&\n+\t\ttest_commit c-first &&\n+\t\ttest_commit c-old-only &&\n+\t\ttest_commit c-common &&\n+\t\tgit switch -C combined-new c-first &&\n+\t\ttest_commit c-new-only &&\n+\t\tgit cherry-pick c-common &&\n+\n+\t\told_only_oid=$(git rev-parse --short=7 c-old-only) &&\n+\t\tnew_only_oid=$(git rev-parse --short=7 c-new-only) &&\n+\t\tcommon_old_oid=$(git rev-parse --short=7 c-common) &&\n+\t\tcommon_new_oid=$(git rev-parse --short=7 HEAD) &&\n+\n+\t\tgit range-diff -s --abbrev=7 combined-old...combined-new >actual &&\n+\t\tcat >expect <<-EOF &&\n+\t\t1:  $old_only_oid < -:  ------- c-old-only\n+\t\t-:  ------- > 1:  $new_only_oid c-new-only\n+\t\t2:  $common_old_oid = 2:  $common_new_oid c-common\n+\t\tEOF\n+\t\ttest_cmp expect actual &&\n+\n+\t\tgit range-diff -s --abbrev=7 --left-only combined-old...combined-new \\\n+\t\t\t>actual &&\n+\t\tcat >expect <<-EOF &&\n+\t\t1:  $old_only_oid < -:  ------- c-old-only\n+\t\t2:  $common_old_oid = 2:  $common_new_oid c-common\n+\t\tEOF\n+\t\ttest_cmp expect actual &&\n+\n+\t\tgit range-diff -s --abbrev=7 --right-only combined-old...combined-new \\\n+\t\t\t>actual &&\n+\t\tcat >expect <<-EOF &&\n+\t\t-:  ------- > 1:  $new_only_oid c-new-only\n+\t\t2:  $common_old_oid = 2:  $common_new_oid c-common\n+\t\tEOF\n+\t\ttest_cmp expect actual &&\n+\n+\t\tgit range-diff -s --abbrev=7 --matched-only combined-old...combined-new \\\n+\t\t\t>actual &&\n+\t\techo \"2:  $common_old_oid = 2:  $common_new_oid c-common\" >expect &&\n+\t\ttest_cmp expect actual\n+\t)\n+'\n+\n test_expect_success 'ranges with pathspecs' '\n \tgit range-diff topic...mode-only-change -- other-file >actual &&\n \ttest_line_count = 2 actual &&\n\nbase-commit: 47ce80527c56f462cb97db4ca8125342204d3783\n-- \ngitgitgadget\n"},{"id":"552716","messageId":"xmqqo6dz6bdw.fsf@gitster.g","threadId":"66310","inReplyTo":"pull.2401.v2.git.git.1789160138305.gitgitgadget@gmail.com","subject":"Re: [PATCH v2] range-diff: add --matched-only to skip one-sided commits","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2026-09-14T16:33:31Z","receivedAt":"2026-09-14T16:33:33Z","isPatch":true,"body":"\"Harald Nordgren via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n\n> -\tif (range_diff_opts->left_only && range_diff_opts->right_only)\n> -\t\tres = error(_(\"options '%s' and '%s' cannot be used together\"), \"--left-only\", \"--right-only\");\n> +\tdie_for_incompatible_opt3(range_diff_opts->left_only, \"--left-only\",\n> +\t\t\t\t  range_diff_opts->right_only, \"--right-only\",\n> +\t\t\t\t  range_diff_opts->matched_only, \"--matched-only\");\n\nSorry for suggesting this change during the review.  After thinking\nabout it a bit more, I think it is a bad change.\n\nTop-level range-diff.c should be kept reusable from different\nfront-ends, some of which may invent different ways to deal with\nerrors coming from incorrect end-user settings.  It would rob them\nof an opportunity to recover if we called die() from here like this.\nCalling error() and signaling an error to the caller, as the original\ncode did, was the right thing to do.\n\nIn other words, this adds an unnecessary layering violation, and the\noriginal code was much better from that point of view.\n\nWe might eventually want to have error_for_incompatible_optN() so\nthat this hunk becomes something like:\n\n-\tif (range_diff_opts->left_only && range_diff_opts->right_only)\n-\t\tres = error(_(\"options '%s' and '%s' cannot be used together\"), \"--left-only\", \"--right-only\");\n+\tres |= incompatible_opt_error(\"--left-only\", range_diff_opts->left_only,\n+\t\t\t\t      \"--right-only\", range_diff_opts->right_only,\n+\t\t\t\t      \"--matched-only\", range_diff_opts->matched_only,\n+\t\t\t\t      NULL);\n\nbut I think we should leave that outside this topic.\n\nIn general, whenever I need to say \"something like\" (as opposed to\n\"exactly this\") in a suggestion, it is a sign that the overall idea\nmight be OK, but the details need to be worked out and would take a\nlot of time.  I think this is one of those cases that are better\nhandled as a separate topic without holding this change hostage.\n\nThanks.\n"},{"id":"552743","messageId":"pull.2401.v3.git.git.1789458703432.gitgitgadget@gmail.com","threadId":"66310","inReplyTo":"pull.2401.git.git.1789144877632.gitgitgadget@gmail.com","subject":"[PATCH v3] range-diff: add --matched-only to skip one-sided commits","fromName":"Harald Nordgren via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2026-09-15T07:51:43Z","receivedAt":"2026-09-15T07:51:45Z","isPatch":true,"body":"From: Harald Nordgren <haraldnordgren@gmail.com>\n\nReviewing a range-diff often means scrolling past commits that were\nsimply added or dropped, when only the ones that correspond between\nthe two ranges are of interest.\n\n--left-only and --right-only already each suppress one of those\none-sided groups, but they are defined as \"only show this side\" and\nso cannot be given together, which is exactly why show_range_diff()\nalready rejected that combination. Give the \"show only the commits\nthat correspond on both sides\" behavior its own name, --matched-only,\ninstead of asking users to reach for a combination that errors out.\n\nExtend the existing '--left-only'/'--right-only' conflict check to\nalso reject any combination with --matched-only, since all three\nnarrow the output in ways that cannot be combined.\n\nSigned-off-by: Harald Nordgren <haraldnordgren@gmail.com>\n---\n    range-diff: add --matched-only to skip one-sided commits\n    \n    Add git range-diff --matched-only to only show commits that correspond\n    between the two ranges, skipping ones that were only added or only\n    removed.\n    \n    Changes in v2:\n    \n     * Update docs and commit message.\n     * Use die_for_incompatible_opt3.\n\nPublished-As: https://github.com/gitgitgadget/git/releases/tag/pr-git-2401%2FHaraldNordgren%2Frange-diff-matched-only-v3\nFetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-git-2401/HaraldNordgren/range-diff-matched-only-v3\nPull-Request: https://github.com/git/git/pull/2401\n\nRange-diff vs v2:\n\n 1:  6d392249a2 ! 1:  ee195c14fc range-diff: add --matched-only to skip one-sided commits\n     @@ builtin/range-diff.c: int cmd_range_diff(int argc,\n       \tstrvec_clear(&log_arg);\n      \n       ## range-diff.c ##\n     -@@\n     - #include \"userdiff.h\"\n     - #include \"apply.h\"\n     - #include \"revision.h\"\n     -+#include \"parse-options.h\"\n     - \n     - struct patch_util {\n     - \t/* For the search for an exact match */\n      @@ range-diff.c: int show_range_diff(const char *range1, const char *range2,\n       \tstruct string_list branch2 = STRING_LIST_INIT_DUP;\n       \tunsigned int include_merges = range_diff_opts->include_merges;\n       \n      -\tif (range_diff_opts->left_only && range_diff_opts->right_only)\n      -\t\tres = error(_(\"options '%s' and '%s' cannot be used together\"), \"--left-only\", \"--right-only\");\n     -+\tdie_for_incompatible_opt3(range_diff_opts->left_only, \"--left-only\",\n     -+\t\t\t\t  range_diff_opts->right_only, \"--right-only\",\n     -+\t\t\t\t  range_diff_opts->matched_only, \"--matched-only\");\n     - \n     --\tif (!res && read_patches(range1, &branch1, range_diff_opts->log_arg, include_merges))\n     ++\tif (range_diff_opts->left_only + range_diff_opts->right_only +\n     ++\t    range_diff_opts->matched_only > 1)\n     ++\t\tres = error(_(\"options '%s', '%s', or '%s' cannot be used together\"),\n     ++\t\t\t    \"--left-only\", \"--right-only\", \"--matched-only\");\n     ++\n      +\tif (range_diff_opts->matched_only) {\n      +\t\trange_diff_opts->left_only = 1;\n      +\t\trange_diff_opts->right_only = 1;\n      +\t}\n     -+\n     -+\tif (read_patches(range1, &branch1, range_diff_opts->log_arg, include_merges))\n     + \n     + \tif (!res && read_patches(range1, &branch1, range_diff_opts->log_arg, include_merges))\n       \t\tres = error(_(\"could not parse log for '%s'\"), range1);\n     - \tif (!res && read_patches(range2, &branch2, range_diff_opts->log_arg, include_merges))\n     - \t\tres = error(_(\"could not parse log for '%s'\"), range2);\n      \n       ## range-diff.h ##\n      @@\n\n\n Documentation/git-range-diff.adoc |  9 ++++-\n builtin/range-diff.c              |  5 ++-\n range-diff.c                      | 11 +++++-\n range-diff.h                      |  2 +-\n t/t3206-range-diff.sh             | 63 +++++++++++++++++++++++++++++++\n 5 files changed, 85 insertions(+), 5 deletions(-)\n\ndiff --git a/Documentation/git-range-diff.adoc b/Documentation/git-range-diff.adoc\nindex 5cc5e2ed56..d448f897d6 100644\n--- a/Documentation/git-range-diff.adoc\n+++ b/Documentation/git-range-diff.adoc\n@@ -10,7 +10,8 @@ SYNOPSIS\n [synopsis]\n git range-diff [--color=[<when>]] [--no-color] [<diff-options>]\n \t[--no-dual-color] [--creation-factor=<factor>]\n-\t[--left-only | --right-only] [--diff-merges=<format>]\n+\t[--left-only | --right-only | --matched-only]\n+\t[--diff-merges=<format>]\n \t[--remerge-diff] [--no-notes | --notes[=<ref>]]\n \t( <range1> <range2> | <rev1>...<rev2> | <base> <rev1> <rev2> )\n \t[[--] <path>...]\n@@ -82,6 +83,12 @@ to revert to color all lines according to the outer diff markers\n \tSuppress commits that are missing from the second specified range\n \t(or the \"right range\" when using the `<rev1>...<rev2>` form).\n \n+`--matched-only`::\n+\tOnly emit commits that have a corresponding commit in the other\n+\trange, suppressing any commit that exists on only one side. Useful\n+\tto skip added or removed commits when reviewing how the commits\n+\tthat survived a rebase changed.\n+\n `--diff-merges=<format>`::\n \tInstead of ignoring merge commits, generate diffs for them using the\n \tcorresponding `--diff-merges=<format>` option of linkgit:git-log[1],\ndiff --git a/builtin/range-diff.c b/builtin/range-diff.c\nindex e54c0f7fe1..8059f92eaa 100644\n--- a/builtin/range-diff.c\n+++ b/builtin/range-diff.c\n@@ -46,7 +46,7 @@ int cmd_range_diff(int argc,\n \t\t.diffopt = &diffopt,\n \t\t.log_arg = &log_arg\n \t};\n-\tint simple_color = -1, left_only = 0, right_only = 0;\n+\tint simple_color = -1, left_only = 0, right_only = 0, matched_only = 0;\n \tstruct option range_diff_options[] = {\n \t\tOPT_INTEGER(0, \"creation-factor\",\n \t\t\t    &range_diff_opts.creation_factor,\n@@ -68,6 +68,8 @@ int cmd_range_diff(int argc,\n \t\t\t N_(\"only emit output related to the first range\")),\n \t\tOPT_BOOL(0, \"right-only\", &right_only,\n \t\t\t N_(\"only emit output related to the second range\")),\n+\t\tOPT_BOOL(0, \"matched-only\", &matched_only,\n+\t\t\t N_(\"only emit commits that have a corresponding commit in the other range\")),\n \t\tOPT_END()\n \t};\n \tstruct option *options;\n@@ -186,6 +188,7 @@ int cmd_range_diff(int argc,\n \trange_diff_opts.dual_color = simple_color < 1;\n \trange_diff_opts.left_only = left_only;\n \trange_diff_opts.right_only = right_only;\n+\trange_diff_opts.matched_only = matched_only;\n \tres = show_range_diff(range1.buf, range2.buf, &range_diff_opts);\n \n \tstrvec_clear(&log_arg);\ndiff --git a/range-diff.c b/range-diff.c\nindex 8e2dd2eb19..fa895f5760 100644\n--- a/range-diff.c\n+++ b/range-diff.c\n@@ -591,8 +591,15 @@ int show_range_diff(const char *range1, const char *range2,\n \tstruct string_list branch2 = STRING_LIST_INIT_DUP;\n \tunsigned int include_merges = range_diff_opts->include_merges;\n \n-\tif (range_diff_opts->left_only && range_diff_opts->right_only)\n-\t\tres = error(_(\"options '%s' and '%s' cannot be used together\"), \"--left-only\", \"--right-only\");\n+\tif (range_diff_opts->left_only + range_diff_opts->right_only +\n+\t    range_diff_opts->matched_only > 1)\n+\t\tres = error(_(\"options '%s', '%s', or '%s' cannot be used together\"),\n+\t\t\t    \"--left-only\", \"--right-only\", \"--matched-only\");\n+\n+\tif (range_diff_opts->matched_only) {\n+\t\trange_diff_opts->left_only = 1;\n+\t\trange_diff_opts->right_only = 1;\n+\t}\n \n \tif (!res && read_patches(range1, &branch1, range_diff_opts->log_arg, include_merges))\n \t\tres = error(_(\"could not parse log for '%s'\"), range1);\ndiff --git a/range-diff.h b/range-diff.h\nindex 9b70a80009..effd10b9b8 100644\n--- a/range-diff.h\n+++ b/range-diff.h\n@@ -19,7 +19,7 @@\n struct range_diff_options {\n \tint creation_factor;\n \tunsigned dual_color:1;\n-\tunsigned left_only:1, right_only:1;\n+\tunsigned left_only:1, right_only:1, matched_only:1;\n \tunsigned include_merges:1;\n \tsize_t max_memory;\n \tconst struct diff_options *diffopt; /* may be NULL */\ndiff --git a/t/t3206-range-diff.sh b/t/t3206-range-diff.sh\nindex ef92704de3..f85fd0c4ad 100755\n--- a/t/t3206-range-diff.sh\n+++ b/t/t3206-range-diff.sh\n@@ -860,6 +860,69 @@ test_expect_success '--left-only/--right-only' '\n \ttest_cmp expect actual\n '\n \n+test_expect_success '--left-only, --right-only and --matched-only are incompatible' '\n+\ttest_must_fail git range-diff --left-only --right-only ...common 2>err &&\n+\ttest_grep \"cannot be used together\" err &&\n+\n+\ttest_must_fail git range-diff --left-only --matched-only ...common 2>err &&\n+\ttest_grep \"cannot be used together\" err &&\n+\n+\ttest_must_fail git range-diff --right-only --matched-only ...common 2>err &&\n+\ttest_grep \"cannot be used together\" err &&\n+\n+\ttest_must_fail git range-diff --left-only --right-only --matched-only \\\n+\t\t...common 2>err &&\n+\ttest_grep \"cannot be used together\" err\n+'\n+\n+test_expect_success '--left-only, --right-only and --matched-only each suppress one-sided commits' '\n+\ttest_create_repo matched-only &&\n+\t(\n+\t\tcd matched-only &&\n+\t\tgit switch --orphan combined-old &&\n+\t\ttest_commit c-first &&\n+\t\ttest_commit c-old-only &&\n+\t\ttest_commit c-common &&\n+\t\tgit switch -C combined-new c-first &&\n+\t\ttest_commit c-new-only &&\n+\t\tgit cherry-pick c-common &&\n+\n+\t\told_only_oid=$(git rev-parse --short=7 c-old-only) &&\n+\t\tnew_only_oid=$(git rev-parse --short=7 c-new-only) &&\n+\t\tcommon_old_oid=$(git rev-parse --short=7 c-common) &&\n+\t\tcommon_new_oid=$(git rev-parse --short=7 HEAD) &&\n+\n+\t\tgit range-diff -s --abbrev=7 combined-old...combined-new >actual &&\n+\t\tcat >expect <<-EOF &&\n+\t\t1:  $old_only_oid < -:  ------- c-old-only\n+\t\t-:  ------- > 1:  $new_only_oid c-new-only\n+\t\t2:  $common_old_oid = 2:  $common_new_oid c-common\n+\t\tEOF\n+\t\ttest_cmp expect actual &&\n+\n+\t\tgit range-diff -s --abbrev=7 --left-only combined-old...combined-new \\\n+\t\t\t>actual &&\n+\t\tcat >expect <<-EOF &&\n+\t\t1:  $old_only_oid < -:  ------- c-old-only\n+\t\t2:  $common_old_oid = 2:  $common_new_oid c-common\n+\t\tEOF\n+\t\ttest_cmp expect actual &&\n+\n+\t\tgit range-diff -s --abbrev=7 --right-only combined-old...combined-new \\\n+\t\t\t>actual &&\n+\t\tcat >expect <<-EOF &&\n+\t\t-:  ------- > 1:  $new_only_oid c-new-only\n+\t\t2:  $common_old_oid = 2:  $common_new_oid c-common\n+\t\tEOF\n+\t\ttest_cmp expect actual &&\n+\n+\t\tgit range-diff -s --abbrev=7 --matched-only combined-old...combined-new \\\n+\t\t\t>actual &&\n+\t\techo \"2:  $common_old_oid = 2:  $common_new_oid c-common\" >expect &&\n+\t\ttest_cmp expect actual\n+\t)\n+'\n+\n test_expect_success 'ranges with pathspecs' '\n \tgit range-diff topic...mode-only-change -- other-file >actual &&\n \ttest_line_count = 2 actual &&\n\nbase-commit: 3699d22b59a6ea467ce13edb81b6bdea0398c803\n-- \ngitgitgadget\n"}]}