{"thread":{"id":"58351","subject":"[PATCH 0/2] Allow passing pathspecs to git range-diff","startedAt":"2022-08-23T16:14:53Z","lastAt":"2022-08-26T17:02:46Z","messageCount":10,"participants":["Johannes Schindelin via GitGitGadget","Junio C Hamano"],"isPatch":true,"patchVersion":1,"patchTotal":2},"messages":[{"id":"461847","messageId":"pull.1335.git.1661258122.gitgitgadget@gmail.com","threadId":"58351","inReplyTo":null,"subject":"[PATCH 0/2] Allow passing pathspecs to git range-diff","fromName":"Johannes Schindelin via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2022-08-23T12:35:20Z","receivedAt":"2022-08-23T16:14:53Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"I just had the need to find out upstream commits corresponding to a handful\nof backported commits, and most importantly, identify upstream commits\ntouching a given file that had not yet been backported.\n\nThis new mode helped me identify them.\n\nJohannes Schindelin (2):\n  range-diff: reorder argument handling\n  range-diff: optionally accept a pathspec\n\n Documentation/git-range-diff.txt |  4 +++\n builtin/range-diff.c             | 54 ++++++++++++++++++++++++--------\n range-diff.c                     |  2 +-\n t/t3206-range-diff.sh            | 11 +++++++\n 4 files changed, 57 insertions(+), 14 deletions(-)\n\n\nbase-commit: 795ea8776befc95ea2becd8020c7a284677b4161\nPublished-As: https://github.com/gitgitgadget/git/releases/tag/pr-1335%2Fdscho%2Frange-diff-with-pathspec-v1\nFetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-1335/dscho/range-diff-with-pathspec-v1\nPull-Request: https://github.com/gitgitgadget/git/pull/1335\n-- \ngitgitgadget\n"},{"id":"461848","messageId":"7fe4f228ae0a88b99b489018c076a7008642610e.1661258122.git.gitgitgadget@gmail.com","threadId":"58351","inReplyTo":"pull.1335.git.1661258122.gitgitgadget@gmail.com","subject":"[PATCH 1/2] range-diff: reorder argument handling","fromName":"Johannes Schindelin via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2022-08-23T12:35:21Z","receivedAt":"2022-08-23T16:14:55Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"From: Johannes Schindelin <johannes.schindelin@gmx.de>\n\nIn d9c66f0b5bf (range-diff: first rudimentary implementation,\n2018-08-13), we introduced the argument handling of the `range-diff`\ncommand, special-casing three different stanzas based on the argument\ncount.\n\nThe somewhat unorthodox order (first handling the case of 2 arguments,\nthen 3, then 1) was chosen for clarity: the natural argument number is 2\nbecause that is how many revision ranges are used internally. The code\nto handle three arguments is relatively trivial, so it was added next.\nAnd finally, the code to ungarble a single symmetric range into two\nseparate ones was added, because it was the most complicated (the most\ninelegant part being about interpreting empty sides of the symmetric\nrange as `HEAD`).\n\nIn preparation for allowing pathspecs in `git range-diff` invocations,\nwhere we no longer have the luxury of using the number of arguments to\ndisambiguate between these three different ways to specify the commit\nranges, we need to order these cases by argument count, in ascending\norder.\n\nThis patch is best viewed with `--color-moved`.\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n builtin/range-diff.c | 24 ++++++++++++------------\n 1 file changed, 12 insertions(+), 12 deletions(-)\n\ndiff --git a/builtin/range-diff.c b/builtin/range-diff.c\nindex 50318849d65..c8ffcd35aea 100644\n--- a/builtin/range-diff.c\n+++ b/builtin/range-diff.c\n@@ -55,18 +55,7 @@ int cmd_range_diff(int argc, const char **argv, const char *prefix)\n \tif (!simple_color)\n \t\tdiffopt.use_color = 1;\n \n-\tif (argc == 2) {\n-\t\tif (!is_range_diff_range(argv[0]))\n-\t\t\tdie(_(\"not a commit range: '%s'\"), argv[0]);\n-\t\tstrbuf_addstr(&range1, argv[0]);\n-\n-\t\tif (!is_range_diff_range(argv[1]))\n-\t\t\tdie(_(\"not a commit range: '%s'\"), argv[1]);\n-\t\tstrbuf_addstr(&range2, argv[1]);\n-\t} else if (argc == 3) {\n-\t\tstrbuf_addf(&range1, \"%s..%s\", argv[0], argv[1]);\n-\t\tstrbuf_addf(&range2, \"%s..%s\", argv[0], argv[2]);\n-\t} else if (argc == 1) {\n+\tif (argc == 1) {\n \t\tconst char *b = strstr(argv[0], \"...\"), *a = argv[0];\n \t\tint a_len;\n \n@@ -85,6 +74,17 @@ int cmd_range_diff(int argc, const char **argv, const char *prefix)\n \t\t\tb = \"HEAD\";\n \t\tstrbuf_addf(&range1, \"%s..%.*s\", b, a_len, a);\n \t\tstrbuf_addf(&range2, \"%.*s..%s\", a_len, a, b);\n+\t} else if (argc == 2) {\n+\t\tif (!is_range_diff_range(argv[0]))\n+\t\t\tdie(_(\"not a commit range: '%s'\"), argv[0]);\n+\t\tstrbuf_addstr(&range1, argv[0]);\n+\n+\t\tif (!is_range_diff_range(argv[1]))\n+\t\t\tdie(_(\"not a commit range: '%s'\"), argv[1]);\n+\t\tstrbuf_addstr(&range2, argv[1]);\n+\t} else if (argc == 3) {\n+\t\tstrbuf_addf(&range1, \"%s..%s\", argv[0], argv[1]);\n+\t\tstrbuf_addf(&range2, \"%s..%s\", argv[0], argv[2]);\n \t} else {\n \t\terror(_(\"need two commit ranges\"));\n \t\tusage_with_options(builtin_range_diff_usage, options);\n-- \ngitgitgadget\n\n"},{"id":"461849","messageId":"064b147451b04051a413b532cd97ae764ba68027.1661258122.git.gitgitgadget@gmail.com","threadId":"58351","inReplyTo":"pull.1335.git.1661258122.gitgitgadget@gmail.com","subject":"[PATCH 2/2] range-diff: optionally accept a pathspec","fromName":"Johannes Schindelin via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2022-08-23T12:35:22Z","receivedAt":"2022-08-23T16:15:15Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"From: Johannes Schindelin <johannes.schindelin@gmx.de>\n\nThe `git range-diff` command can be quite expensive, which is not a\nsurprise given that the underlying algorithm to match up pairs of\ncommits between the provided two commit ranges has a cubic runtime.\n\nTherefore it makes sense to restrict the commit ranges as much as\npossible, to reduce the amount of input to that O(N^3) algorithm.\n\nIn chatty repositories with wide trees, this is not necessarily\npossible merely by choosing commit ranges wisely.\n\nLet's give users another option to restrict the commit ranges: by\nproviding a pathspec. That helps in repositories with wide trees because\nit is likely that the user has a good idea which subset of the tree they\nare actually interested in.\n\nExample:\n\n\tgit range-diff upstream/main upstream/seen HEAD -- range-diff.c\n\nThis shows commits that are either in the local branch or in `seen`, but\nnot in `main`, skipping all commits that do not touch `range-diff.c`.\n\nNote: Since we piggy-back the pathspecs onto the `other_arg` mechanism\nthat was introduced to be able to pass through the `--notes` option to\nthe revision machinery, we must now ensure that the `other_arg` array is\nappended at the end (the revision range must come before the pathspecs,\nif any).\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n Documentation/git-range-diff.txt |  4 ++++\n builtin/range-diff.c             | 30 +++++++++++++++++++++++++++++-\n range-diff.c                     |  2 +-\n t/t3206-range-diff.sh            | 11 +++++++++++\n 4 files changed, 45 insertions(+), 2 deletions(-)\n\ndiff --git a/Documentation/git-range-diff.txt b/Documentation/git-range-diff.txt\nindex fe350d7f405..0b393715d70 100644\n--- a/Documentation/git-range-diff.txt\n+++ b/Documentation/git-range-diff.txt\n@@ -12,6 +12,7 @@ SYNOPSIS\n \t[--no-dual-color] [--creation-factor=<factor>]\n \t[--left-only | --right-only]\n \t( <range1> <range2> | <rev1>...<rev2> | <base> <rev1> <rev2> )\n+\t[[--] <path>...]\n \n DESCRIPTION\n -----------\n@@ -19,6 +20,9 @@ DESCRIPTION\n This command shows the differences between two versions of a patch\n series, or more generally, two commit ranges (ignoring merge commits).\n \n+In the presence of `<path>` arguments, these commit ranges are limited\n+accordingly.\n+\n To that end, it first finds pairs of commits from both commit ranges\n that correspond with each other. Two commits are said to correspond when\n the diff between their patches (i.e. the author information, the commit\ndiff --git a/builtin/range-diff.c b/builtin/range-diff.c\nindex c8ffcd35aea..9ae95b9c950 100644\n--- a/builtin/range-diff.c\n+++ b/builtin/range-diff.c\n@@ -40,6 +40,8 @@ int cmd_range_diff(int argc, const char **argv, const char *prefix)\n \tstruct option *options;\n \tint res = 0;\n \tstruct strbuf range1 = STRBUF_INIT, range2 = STRBUF_INIT;\n+\tstruct object_id oid;\n+\tconst char *p;\n \n \tgit_config(git_diff_ui_config, NULL);\n \n@@ -47,7 +49,7 @@ int cmd_range_diff(int argc, const char **argv, const char *prefix)\n \n \toptions = parse_options_concat(range_diff_options, diffopt.parseopts);\n \targc = parse_options(argc, argv, prefix, options,\n-\t\t\t     builtin_range_diff_usage, 0);\n+\t\t\t     builtin_range_diff_usage, PARSE_OPT_KEEP_DASHDASH);\n \n \tdiff_setup_done(&diffopt);\n \n@@ -74,6 +76,20 @@ int cmd_range_diff(int argc, const char **argv, const char *prefix)\n \t\t\tb = \"HEAD\";\n \t\tstrbuf_addf(&range1, \"%s..%.*s\", b, a_len, a);\n \t\tstrbuf_addf(&range2, \"%.*s..%s\", a_len, a, b);\n+\t} else if (argc > 1 && (p = strstr(argv[0], \"...\"))) {\n+\t\tconst char *a = argv[0];\n+\t\tint a_len = (int)(p - a);\n+\n+\t\tif (!a_len) {\n+\t\t\ta = \"HEAD\";\n+\t\t\ta_len = strlen(a);\n+\t\t}\n+\t\tp += 3;\n+\t\tif (!*p)\n+\t\t\tp = \"HEAD\";\n+\t\tstrbuf_addf(&range1, \"%s..%.*s\", p, a_len, a);\n+\t\tstrbuf_addf(&range2, \"%.*s..%s\", a_len, a, p);\n+\t\tstrvec_pushv(&other_arg, argv + 1);\n \t} else if (argc == 2) {\n \t\tif (!is_range_diff_range(argv[0]))\n \t\t\tdie(_(\"not a commit range: '%s'\"), argv[0]);\n@@ -82,9 +98,21 @@ int cmd_range_diff(int argc, const char **argv, const char *prefix)\n \t\tif (!is_range_diff_range(argv[1]))\n \t\t\tdie(_(\"not a commit range: '%s'\"), argv[1]);\n \t\tstrbuf_addstr(&range2, argv[1]);\n+\t} else if (argc > 2 &&\n+\t    is_range_diff_range(argv[0]) && is_range_diff_range(argv[1])) {\n+\t\tstrbuf_addstr(&range1, argv[0]);\n+\t\tstrbuf_addstr(&range2, argv[1]);\n+\t\tstrvec_pushv(&other_arg, argv + 2);\n \t} else if (argc == 3) {\n \t\tstrbuf_addf(&range1, \"%s..%s\", argv[0], argv[1]);\n \t\tstrbuf_addf(&range2, \"%s..%s\", argv[0], argv[2]);\n+\t} else if (argc > 3 &&\n+\t\t   get_oid_committish(argv[0], &oid) &&\n+\t\t   get_oid_committish(argv[1], &oid) &&\n+\t\t   get_oid_committish(argv[2], &oid)) {\n+\t\tstrbuf_addf(&range1, \"%s..%s\", argv[0], argv[1]);\n+\t\tstrbuf_addf(&range2, \"%s..%s\", argv[0], argv[2]);\n+\t\tstrvec_pushv(&other_arg, argv + 3);\n \t} else {\n \t\terror(_(\"need two commit ranges\"));\n \t\tusage_with_options(builtin_range_diff_usage, options);\ndiff --git a/range-diff.c b/range-diff.c\nindex f63b3ffc200..124dd678c38 100644\n--- a/range-diff.c\n+++ b/range-diff.c\n@@ -57,9 +57,9 @@ static int read_patches(const char *range, struct string_list *list,\n \t\t     \"--pretty=medium\",\n \t\t     \"--notes\",\n \t\t     NULL);\n+\tstrvec_push(&cp.args, range);\n \tif (other_arg)\n \t\tstrvec_pushv(&cp.args, other_arg->v);\n-\tstrvec_push(&cp.args, range);\n \tcp.out = -1;\n \tcp.no_stdin = 1;\n \tcp.git_cmd = 1;\ndiff --git a/t/t3206-range-diff.sh b/t/t3206-range-diff.sh\nindex d12e4e4cc6c..f2821a69b6f 100755\n--- a/t/t3206-range-diff.sh\n+++ b/t/t3206-range-diff.sh\n@@ -772,6 +772,17 @@ test_expect_success '--left-only/--right-only' '\n \ttest_cmp expect actual\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+\ttopic_oid=$(git rev-parse --short topic) &&\n+\tmode_change_oid=$(git rev-parse --short mode-only-change^) &&\n+\tfile_change_oid=$(git rev-parse --short mode-only-change) &&\n+\tgrep \"$mode_change_oid\" actual &&\n+\t! grep \"$file_change_oid\" actual &&\n+\t! grep \"$topic_oid\" actual\n+'\n+\n test_expect_success 'submodule changes are shown irrespective of diff.submodule' '\n \tgit init sub-repo &&\n \ttest_commit -C sub-repo sub-first &&\n-- \ngitgitgadget\n"},{"id":"461910","messageId":"xmqqh7215pd0.fsf@gitster.g","threadId":"58351","inReplyTo":"064b147451b04051a413b532cd97ae764ba68027.1661258122.git.gitgitgadget@gmail.com","subject":"Re: [PATCH 2/2] range-diff: optionally accept a pathspec","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2022-08-24T21:00:11Z","receivedAt":"2022-08-24T21:00:15Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"\"Johannes Schindelin via GitGitGadget\" <gitgitgadget@gmail.com>\nwrites:\n\n> From: Johannes Schindelin <johannes.schindelin@gmx.de>\n>\n> The `git range-diff` command can be quite expensive, which is not a\n> surprise given that the underlying algorithm to match up pairs of\n> commits between the provided two commit ranges has a cubic runtime.\n>\n> Therefore it makes sense to restrict the commit ranges as much as\n> possible, to reduce the amount of input to that O(N^3) algorithm.\n>\n> In chatty repositories with wide trees, this is not necessarily\n> possible merely by choosing commit ranges wisely.\n>\n> Let's give users another option to restrict the commit ranges: by\n> providing a pathspec. That helps in repositories with wide trees because\n> it is likely that the user has a good idea which subset of the tree they\n> are actually interested in.\n>\n> Example:\n>\n> \tgit range-diff upstream/main upstream/seen HEAD -- range-diff.c\n>\n> This shows commits that are either in the local branch or in `seen`, but\n> not in `main`, skipping all commits that do not touch `range-diff.c`.\n>\n> Note: Since we piggy-back the pathspecs onto the `other_arg` mechanism\n> that was introduced to be able to pass through the `--notes` option to\n> the revision machinery, we must now ensure that the `other_arg` array is\n> appended at the end (the revision range must come before the pathspecs,\n> if any).\n>\n> Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n> ---\n\nIt is excellent that this now takes pathspec to sparsify the\nhistory.\n\nThe implementation looks, eh, a bit dirty with obvious repetitions\nin the \"...\" case and two-ranges cases.  Three-arg cases sort-of\nlooks different but that is because the original one does not even\nbother to ensure argv[0] and argv[1] are objects, while the new one\ndoes, so they are essentially doing the same things.\n\n> diff --git a/Documentation/git-range-diff.txt b/Documentation/git-range-diff.txt\n> index fe350d7f405..0b393715d70 100644\n> --- a/Documentation/git-range-diff.txt\n> +++ b/Documentation/git-range-diff.txt\n> @@ -12,6 +12,7 @@ SYNOPSIS\n>  \t[--no-dual-color] [--creation-factor=<factor>]\n>  \t[--left-only | --right-only]\n>  \t( <range1> <range2> | <rev1>...<rev2> | <base> <rev1> <rev2> )\n> +\t[[--] <path>...]\n\nOK.\n"},{"id":"461987","messageId":"pull.1335.v2.git.1661506770.gitgitgadget@gmail.com","threadId":"58351","inReplyTo":"pull.1335.git.1661258122.gitgitgadget@gmail.com","subject":"[PATCH v2 0/3] Allow passing pathspecs to git range-diff","fromName":"Johannes Schindelin via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2022-08-26T09:39:27Z","receivedAt":"2022-08-26T09:39:38Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"I just had the need to find out upstream commits corresponding to a handful\nof backported commits, and most importantly, identify upstream commits\ntouching a given file that had not yet been backported.\n\nThis new mode helped me identify them.\n\nChanges since v1:\n\n * The command-line parameter parsing now avoids duplicating code as much as\n   possible.\n * This also fixes a bug where git range-diff <incorrect-symmetric-range> --\n   <pathspec> was mistaken for using the three-revision stanza.\n * Consistent validation of the command-line arguments has been extracted\n   into its own patch.\n * Sadly, these changes make the overall diff much larger. I hope that the\n   readability is worth that price.\n\nJohannes Schindelin (3):\n  range-diff: reorder argument handling\n  range-diff: consistently validate the arguments\n  range-diff: optionally accept pathspecs\n\n Documentation/git-range-diff.txt |   4 ++\n builtin/range-diff.c             | 101 +++++++++++++++++++++++--------\n range-diff.c                     |   2 +-\n t/t3206-range-diff.sh            |  13 +++-\n 4 files changed, 94 insertions(+), 26 deletions(-)\n\n\nbase-commit: 795ea8776befc95ea2becd8020c7a284677b4161\nPublished-As: https://github.com/gitgitgadget/git/releases/tag/pr-1335%2Fdscho%2Frange-diff-with-pathspec-v2\nFetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-1335/dscho/range-diff-with-pathspec-v2\nPull-Request: https://github.com/gitgitgadget/git/pull/1335\n\nRange-diff vs v1:\n\n 1:  7fe4f228ae0 ! 1:  150f29a1c48 range-diff: reorder argument handling\n     @@ Commit message\n          In preparation for allowing pathspecs in `git range-diff` invocations,\n          where we no longer have the luxury of using the number of arguments to\n          disambiguate between these three different ways to specify the commit\n     -    ranges, we need to order these cases by argument count, in ascending\n     +    ranges, we need to order these cases by argument count, in descending\n          order.\n      \n          This patch is best viewed with `--color-moved`.\n     @@ builtin/range-diff.c: int cmd_range_diff(int argc, const char **argv, const char\n       \t\tdiffopt.use_color = 1;\n       \n      -\tif (argc == 2) {\n     --\t\tif (!is_range_diff_range(argv[0]))\n     --\t\t\tdie(_(\"not a commit range: '%s'\"), argv[0]);\n     --\t\tstrbuf_addstr(&range1, argv[0]);\n     --\n     --\t\tif (!is_range_diff_range(argv[1]))\n     --\t\t\tdie(_(\"not a commit range: '%s'\"), argv[1]);\n     --\t\tstrbuf_addstr(&range2, argv[1]);\n     ++\tif (argc == 3) {\n     ++\t\tstrbuf_addf(&range1, \"%s..%s\", argv[0], argv[1]);\n     ++\t\tstrbuf_addf(&range2, \"%s..%s\", argv[0], argv[2]);\n     ++\t} else if (argc == 2) {\n     + \t\tif (!is_range_diff_range(argv[0]))\n     + \t\t\tdie(_(\"not a commit range: '%s'\"), argv[0]);\n     + \t\tstrbuf_addstr(&range1, argv[0]);\n     +@@ builtin/range-diff.c: int cmd_range_diff(int argc, const char **argv, const char *prefix)\n     + \t\tif (!is_range_diff_range(argv[1]))\n     + \t\t\tdie(_(\"not a commit range: '%s'\"), argv[1]);\n     + \t\tstrbuf_addstr(&range2, argv[1]);\n      -\t} else if (argc == 3) {\n      -\t\tstrbuf_addf(&range1, \"%s..%s\", argv[0], argv[1]);\n      -\t\tstrbuf_addf(&range2, \"%s..%s\", argv[0], argv[2]);\n     --\t} else if (argc == 1) {\n     -+\tif (argc == 1) {\n     + \t} else if (argc == 1) {\n       \t\tconst char *b = strstr(argv[0], \"...\"), *a = argv[0];\n       \t\tint a_len;\n     - \n     -@@ builtin/range-diff.c: int cmd_range_diff(int argc, const char **argv, const char *prefix)\n     - \t\t\tb = \"HEAD\";\n     - \t\tstrbuf_addf(&range1, \"%s..%.*s\", b, a_len, a);\n     - \t\tstrbuf_addf(&range2, \"%.*s..%s\", a_len, a, b);\n     -+\t} else if (argc == 2) {\n     -+\t\tif (!is_range_diff_range(argv[0]))\n     -+\t\t\tdie(_(\"not a commit range: '%s'\"), argv[0]);\n     -+\t\tstrbuf_addstr(&range1, argv[0]);\n     -+\n     -+\t\tif (!is_range_diff_range(argv[1]))\n     -+\t\t\tdie(_(\"not a commit range: '%s'\"), argv[1]);\n     -+\t\tstrbuf_addstr(&range2, argv[1]);\n     -+\t} else if (argc == 3) {\n     -+\t\tstrbuf_addf(&range1, \"%s..%s\", argv[0], argv[1]);\n     -+\t\tstrbuf_addf(&range2, \"%s..%s\", argv[0], argv[2]);\n     - \t} else {\n     - \t\terror(_(\"need two commit ranges\"));\n     - \t\tusage_with_options(builtin_range_diff_usage, options);\n -:  ----------- > 2:  4cd7f09557c range-diff: consistently validate the arguments\n 2:  064b147451b ! 3:  a52ad40e015 range-diff: optionally accept a pathspec\n     @@ Metadata\n      Author: Johannes Schindelin <Johannes.Schindelin@gmx.de>\n      \n       ## Commit message ##\n     -    range-diff: optionally accept a pathspec\n     +    range-diff: optionally accept pathspecs\n      \n          The `git range-diff` command can be quite expensive, which is not a\n          surprise given that the underlying algorithm to match up pairs of\n     @@ Documentation/git-range-diff.txt: DESCRIPTION\n      \n       ## builtin/range-diff.c ##\n      @@ builtin/range-diff.c: int cmd_range_diff(int argc, const char **argv, const char *prefix)\n     + \t\tOPT_END()\n     + \t};\n       \tstruct option *options;\n     - \tint res = 0;\n     +-\tint res = 0;\n     ++\tint i, dash_dash = -1, res = 0;\n       \tstruct strbuf range1 = STRBUF_INIT, range2 = STRBUF_INIT;\n     -+\tstruct object_id oid;\n     -+\tconst char *p;\n     + \tstruct object_id oid;\n     ++\tconst char *three_dots = NULL;\n       \n       \tgit_config(git_diff_ui_config, NULL);\n       \n     @@ builtin/range-diff.c: int cmd_range_diff(int argc, const char **argv, const char\n       \tdiff_setup_done(&diffopt);\n       \n      @@ builtin/range-diff.c: int cmd_range_diff(int argc, const char **argv, const char *prefix)\n     - \t\t\tb = \"HEAD\";\n     - \t\tstrbuf_addf(&range1, \"%s..%.*s\", b, a_len, a);\n     - \t\tstrbuf_addf(&range2, \"%.*s..%s\", a_len, a, b);\n     -+\t} else if (argc > 1 && (p = strstr(argv[0], \"...\"))) {\n     -+\t\tconst char *a = argv[0];\n     -+\t\tint a_len = (int)(p - a);\n     -+\n     -+\t\tif (!a_len) {\n     -+\t\t\ta = \"HEAD\";\n     -+\t\t\ta_len = strlen(a);\n     + \tif (!simple_color)\n     + \t\tdiffopt.use_color = 1;\n     + \n     +-\tif (argc == 3) {\n     +-\t\tif (get_oid_committish(argv[0], &oid))\n     ++\tfor (i = 0; i < argc; i++)\n     ++\t\tif (!strcmp(argv[i], \"--\")) {\n     ++\t\t\tdash_dash = i;\n     ++\t\t\tbreak;\n      +\t\t}\n     -+\t\tp += 3;\n     -+\t\tif (!*p)\n     -+\t\t\tp = \"HEAD\";\n     -+\t\tstrbuf_addf(&range1, \"%s..%.*s\", p, a_len, a);\n     -+\t\tstrbuf_addf(&range2, \"%.*s..%s\", a_len, a, p);\n     -+\t\tstrvec_pushv(&other_arg, argv + 1);\n     - \t} else if (argc == 2) {\n     - \t\tif (!is_range_diff_range(argv[0]))\n     - \t\t\tdie(_(\"not a commit range: '%s'\"), argv[0]);\n     ++\n     ++\tif (dash_dash == 3 ||\n     ++\t    (dash_dash < 0 && argc > 2 &&\n     ++\t     !get_oid_committish(argv[0], &oid) &&\n     ++\t     !get_oid_committish(argv[1], &oid) &&\n     ++\t     !get_oid_committish(argv[2], &oid))) {\n     ++\t\tif (dash_dash < 0)\n     ++\t\t\t; /* already validated arguments */\n     ++\t\telse if (get_oid_committish(argv[0], &oid))\n     + \t\t\tusage_msg_optf(_(\"not a revision: '%s'\"),\n     + \t\t\t\t       builtin_range_diff_usage, options,\n     + \t\t\t\t       argv[0]);\n      @@ builtin/range-diff.c: int cmd_range_diff(int argc, const char **argv, const char *prefix)\n     - \t\tif (!is_range_diff_range(argv[1]))\n     - \t\t\tdie(_(\"not a commit range: '%s'\"), argv[1]);\n     - \t\tstrbuf_addstr(&range2, argv[1]);\n     -+\t} else if (argc > 2 &&\n     -+\t    is_range_diff_range(argv[0]) && is_range_diff_range(argv[1])) {\n     -+\t\tstrbuf_addstr(&range1, argv[0]);\n     -+\t\tstrbuf_addstr(&range2, argv[1]);\n     -+\t\tstrvec_pushv(&other_arg, argv + 2);\n     - \t} else if (argc == 3) {\n     + \n       \t\tstrbuf_addf(&range1, \"%s..%s\", argv[0], argv[1]);\n       \t\tstrbuf_addf(&range2, \"%s..%s\", argv[0], argv[2]);\n     -+\t} else if (argc > 3 &&\n     -+\t\t   get_oid_committish(argv[0], &oid) &&\n     -+\t\t   get_oid_committish(argv[1], &oid) &&\n     -+\t\t   get_oid_committish(argv[2], &oid)) {\n     -+\t\tstrbuf_addf(&range1, \"%s..%s\", argv[0], argv[1]);\n     -+\t\tstrbuf_addf(&range2, \"%s..%s\", argv[0], argv[2]);\n     -+\t\tstrvec_pushv(&other_arg, argv + 3);\n     - \t} else {\n     - \t\terror(_(\"need two commit ranges\"));\n     - \t\tusage_with_options(builtin_range_diff_usage, options);\n     +-\t} else if (argc == 2) {\n     +-\t\tif (!is_range_diff_range(argv[0]))\n     ++\n     ++\t\tstrvec_pushv(&other_arg, argv +\n     ++\t\t\t     (dash_dash < 0 ? 3 : dash_dash));\n     ++\t} else if (dash_dash == 2 ||\n     ++\t\t   (dash_dash < 0 && argc > 1 &&\n     ++\t\t    is_range_diff_range(argv[0]) &&\n     ++\t\t    is_range_diff_range(argv[1]))) {\n     ++\t\tif (dash_dash < 0)\n     ++\t\t\t; /* already validated arguments */\n     ++\t\telse if (!is_range_diff_range(argv[0]))\n     + \t\t\tusage_msg_optf(_(\"not a commit range: '%s'\"),\n     + \t\t\t\t       builtin_range_diff_usage, options,\n     + \t\t\t\t       argv[0]);\n     +@@ builtin/range-diff.c: int cmd_range_diff(int argc, const char **argv, const char *prefix)\n     + \n     + \t\tstrbuf_addstr(&range1, argv[0]);\n     + \t\tstrbuf_addstr(&range2, argv[1]);\n     +-\t} else if (argc == 1) {\n     +-\t\tconst char *b = strstr(argv[0], \"...\"), *a = argv[0];\n     ++\n     ++\t\tstrvec_pushv(&other_arg, argv +\n     ++\t\t\t     (dash_dash < 0 ? 2 : dash_dash));\n     ++\t} else if (dash_dash == 1 ||\n     ++\t\t   (dash_dash < 0 && argc > 0 &&\n     ++\t\t    (three_dots = strstr(argv[0], \"...\")))) {\n     ++\t\tconst char *a, *b;\n     + \t\tint a_len;\n     + \n     +-\t\tif (!b)\n     ++\t\tif (dash_dash < 0)\n     ++\t\t\t; /* already validated arguments */\n     ++\t\telse if (!(three_dots = strstr(argv[0], \"...\")))\n     + \t\t\tusage_msg_optf(_(\"not a symmetric range: '%s'\"),\n     +-\t\t\t\t       builtin_range_diff_usage, options,\n     +-\t\t\t\t       argv[0]);\n     ++\t\t\t\t\t builtin_range_diff_usage, options,\n     ++\t\t\t\t\t argv[0]);\n     + \n     +-\t\ta_len = (int)(b - a);\n     +-\t\tif (!a_len) {\n     ++\t\tif (three_dots == argv[0]) {\n     + \t\t\ta = \"HEAD\";\n     + \t\t\ta_len = strlen(a);\n     ++\t\t} else {\n     ++\t\t\ta = argv[0];\n     ++\t\t\ta_len = (int)(three_dots - a);\n     + \t\t}\n     +-\t\tb += 3;\n     +-\t\tif (!*b)\n     ++\n     ++\t\tif (three_dots[3])\n     ++\t\t\tb = three_dots + 3;\n     ++\t\telse\n     + \t\t\tb = \"HEAD\";\n     ++\n     + \t\tstrbuf_addf(&range1, \"%s..%.*s\", b, a_len, a);\n     + \t\tstrbuf_addf(&range2, \"%.*s..%s\", a_len, a, b);\n     ++\n     ++\t\tstrvec_pushv(&other_arg, argv +\n     ++\t\t\t     (dash_dash < 0 ? 1 : dash_dash));\n     + \t} else\n     + \t\tusage_msg_opt(_(\"need two commit ranges\"),\n     + \t\t\t      builtin_range_diff_usage, options);\n      \n       ## range-diff.c ##\n      @@ range-diff.c: static int read_patches(const char *range, struct string_list *list,\n     @@ range-diff.c: static int read_patches(const char *range, struct string_list *lis\n       \tcp.git_cmd = 1;\n      \n       ## t/t3206-range-diff.sh ##\n     +@@ t/t3206-range-diff.sh: test_expect_success 'A^! and A^-<n> (unmodified)' '\n     + '\n     + \n     + test_expect_success 'A^{/..} is not mistaken for a range' '\n     +-\ttest_must_fail git range-diff topic^.. topic^{/..} 2>error &&\n     ++\ttest_must_fail git range-diff topic^.. topic^{/..} -- 2>error &&\n     + \ttest_i18ngrep \"not a commit range\" error\n     + '\n     + \n      @@ t/t3206-range-diff.sh: test_expect_success '--left-only/--right-only' '\n       \ttest_cmp expect actual\n       '\n\n-- \ngitgitgadget\n"},{"id":"461988","messageId":"150f29a1c48917577ccaa93c0d4548c871adc1f6.1661506770.git.gitgitgadget@gmail.com","threadId":"58351","inReplyTo":"pull.1335.v2.git.1661506770.gitgitgadget@gmail.com","subject":"[PATCH v2 1/3] range-diff: reorder argument handling","fromName":"Johannes Schindelin via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2022-08-26T09:39:28Z","receivedAt":"2022-08-26T09:39:39Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"From: Johannes Schindelin <johannes.schindelin@gmx.de>\n\nIn d9c66f0b5bf (range-diff: first rudimentary implementation,\n2018-08-13), we introduced the argument handling of the `range-diff`\ncommand, special-casing three different stanzas based on the argument\ncount.\n\nThe somewhat unorthodox order (first handling the case of 2 arguments,\nthen 3, then 1) was chosen for clarity: the natural argument number is 2\nbecause that is how many revision ranges are used internally. The code\nto handle three arguments is relatively trivial, so it was added next.\nAnd finally, the code to ungarble a single symmetric range into two\nseparate ones was added, because it was the most complicated (the most\ninelegant part being about interpreting empty sides of the symmetric\nrange as `HEAD`).\n\nIn preparation for allowing pathspecs in `git range-diff` invocations,\nwhere we no longer have the luxury of using the number of arguments to\ndisambiguate between these three different ways to specify the commit\nranges, we need to order these cases by argument count, in descending\norder.\n\nThis patch is best viewed with `--color-moved`.\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n builtin/range-diff.c | 8 ++++----\n 1 file changed, 4 insertions(+), 4 deletions(-)\n\ndiff --git a/builtin/range-diff.c b/builtin/range-diff.c\nindex 50318849d65..f8d3869d357 100644\n--- a/builtin/range-diff.c\n+++ b/builtin/range-diff.c\n@@ -55,7 +55,10 @@ int cmd_range_diff(int argc, const char **argv, const char *prefix)\n \tif (!simple_color)\n \t\tdiffopt.use_color = 1;\n \n-\tif (argc == 2) {\n+\tif (argc == 3) {\n+\t\tstrbuf_addf(&range1, \"%s..%s\", argv[0], argv[1]);\n+\t\tstrbuf_addf(&range2, \"%s..%s\", argv[0], argv[2]);\n+\t} else if (argc == 2) {\n \t\tif (!is_range_diff_range(argv[0]))\n \t\t\tdie(_(\"not a commit range: '%s'\"), argv[0]);\n \t\tstrbuf_addstr(&range1, argv[0]);\n@@ -63,9 +66,6 @@ int cmd_range_diff(int argc, const char **argv, const char *prefix)\n \t\tif (!is_range_diff_range(argv[1]))\n \t\t\tdie(_(\"not a commit range: '%s'\"), argv[1]);\n \t\tstrbuf_addstr(&range2, argv[1]);\n-\t} else if (argc == 3) {\n-\t\tstrbuf_addf(&range1, \"%s..%s\", argv[0], argv[1]);\n-\t\tstrbuf_addf(&range2, \"%s..%s\", argv[0], argv[2]);\n \t} else if (argc == 1) {\n \t\tconst char *b = strstr(argv[0], \"...\"), *a = argv[0];\n \t\tint a_len;\n-- \ngitgitgadget\n\n"},{"id":"461989","messageId":"4cd7f09557c7d0a4802b07d1e8440015e0a50524.1661506770.git.gitgitgadget@gmail.com","threadId":"58351","inReplyTo":"pull.1335.v2.git.1661506770.gitgitgadget@gmail.com","subject":"[PATCH v2 2/3] range-diff: consistently validate the arguments","fromName":"Johannes Schindelin via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2022-08-26T09:39:29Z","receivedAt":"2022-08-26T09:39:40Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"From: Johannes Schindelin <johannes.schindelin@gmx.de>\n\nThis patch lets `range-diff` validate the arguments not only when\ninvoked with one or two arguments, but also in the code path where three\narguments are handled.\n\nWhile at it, we now use `usage_msg_opt*()` consistently.\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n builtin/range-diff.c | 41 +++++++++++++++++++++++++++++------------\n 1 file changed, 29 insertions(+), 12 deletions(-)\n\ndiff --git a/builtin/range-diff.c b/builtin/range-diff.c\nindex f8d3869d357..71319ed1d84 100644\n--- a/builtin/range-diff.c\n+++ b/builtin/range-diff.c\n@@ -40,6 +40,7 @@ int cmd_range_diff(int argc, const char **argv, const char *prefix)\n \tstruct option *options;\n \tint res = 0;\n \tstruct strbuf range1 = STRBUF_INIT, range2 = STRBUF_INIT;\n+\tstruct object_id oid;\n \n \tgit_config(git_diff_ui_config, NULL);\n \n@@ -56,24 +57,41 @@ int cmd_range_diff(int argc, const char **argv, const char *prefix)\n \t\tdiffopt.use_color = 1;\n \n \tif (argc == 3) {\n+\t\tif (get_oid_committish(argv[0], &oid))\n+\t\t\tusage_msg_optf(_(\"not a revision: '%s'\"),\n+\t\t\t\t       builtin_range_diff_usage, options,\n+\t\t\t\t       argv[0]);\n+\t\telse if (get_oid_committish(argv[1], &oid))\n+\t\t\tusage_msg_optf(_(\"not a revision: '%s'\"),\n+\t\t\t\t       builtin_range_diff_usage, options,\n+\t\t\t\t       argv[1]);\n+\t\telse if (get_oid_committish(argv[2], &oid))\n+\t\t\tusage_msg_optf(_(\"not a revision: '%s'\"),\n+\t\t\t\t       builtin_range_diff_usage, options,\n+\t\t\t\t       argv[2]);\n+\n \t\tstrbuf_addf(&range1, \"%s..%s\", argv[0], argv[1]);\n \t\tstrbuf_addf(&range2, \"%s..%s\", argv[0], argv[2]);\n \t} else if (argc == 2) {\n \t\tif (!is_range_diff_range(argv[0]))\n-\t\t\tdie(_(\"not a commit range: '%s'\"), argv[0]);\n-\t\tstrbuf_addstr(&range1, argv[0]);\n+\t\t\tusage_msg_optf(_(\"not a commit range: '%s'\"),\n+\t\t\t\t       builtin_range_diff_usage, options,\n+\t\t\t\t       argv[0]);\n+\t\telse if (!is_range_diff_range(argv[1]))\n+\t\t\tusage_msg_optf(_(\"not a commit range: '%s'\"),\n+\t\t\t\t       builtin_range_diff_usage, options,\n+\t\t\t\t       argv[1]);\n \n-\t\tif (!is_range_diff_range(argv[1]))\n-\t\t\tdie(_(\"not a commit range: '%s'\"), argv[1]);\n+\t\tstrbuf_addstr(&range1, argv[0]);\n \t\tstrbuf_addstr(&range2, argv[1]);\n \t} else if (argc == 1) {\n \t\tconst char *b = strstr(argv[0], \"...\"), *a = argv[0];\n \t\tint a_len;\n \n-\t\tif (!b) {\n-\t\t\terror(_(\"single arg format must be symmetric range\"));\n-\t\t\tusage_with_options(builtin_range_diff_usage, options);\n-\t\t}\n+\t\tif (!b)\n+\t\t\tusage_msg_optf(_(\"not a symmetric range: '%s'\"),\n+\t\t\t\t       builtin_range_diff_usage, options,\n+\t\t\t\t       argv[0]);\n \n \t\ta_len = (int)(b - a);\n \t\tif (!a_len) {\n@@ -85,10 +103,9 @@ int cmd_range_diff(int argc, const char **argv, const char *prefix)\n \t\t\tb = \"HEAD\";\n \t\tstrbuf_addf(&range1, \"%s..%.*s\", b, a_len, a);\n \t\tstrbuf_addf(&range2, \"%.*s..%s\", a_len, a, b);\n-\t} else {\n-\t\terror(_(\"need two commit ranges\"));\n-\t\tusage_with_options(builtin_range_diff_usage, options);\n-\t}\n+\t} else\n+\t\tusage_msg_opt(_(\"need two commit ranges\"),\n+\t\t\t      builtin_range_diff_usage, options);\n \tFREE_AND_NULL(options);\n \n \trange_diff_opts.dual_color = simple_color < 1;\n-- \ngitgitgadget\n\n"},{"id":"461990","messageId":"a52ad40e0155caf8285b86ec26bbd47cd2240466.1661506770.git.gitgitgadget@gmail.com","threadId":"58351","inReplyTo":"pull.1335.v2.git.1661506770.gitgitgadget@gmail.com","subject":"[PATCH v2 3/3] range-diff: optionally accept pathspecs","fromName":"Johannes Schindelin via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2022-08-26T09:39:30Z","receivedAt":"2022-08-26T09:39:47Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"From: Johannes Schindelin <johannes.schindelin@gmx.de>\n\nThe `git range-diff` command can be quite expensive, which is not a\nsurprise given that the underlying algorithm to match up pairs of\ncommits between the provided two commit ranges has a cubic runtime.\n\nTherefore it makes sense to restrict the commit ranges as much as\npossible, to reduce the amount of input to that O(N^3) algorithm.\n\nIn chatty repositories with wide trees, this is not necessarily\npossible merely by choosing commit ranges wisely.\n\nLet's give users another option to restrict the commit ranges: by\nproviding a pathspec. That helps in repositories with wide trees because\nit is likely that the user has a good idea which subset of the tree they\nare actually interested in.\n\nExample:\n\n\tgit range-diff upstream/main upstream/seen HEAD -- range-diff.c\n\nThis shows commits that are either in the local branch or in `seen`, but\nnot in `main`, skipping all commits that do not touch `range-diff.c`.\n\nNote: Since we piggy-back the pathspecs onto the `other_arg` mechanism\nthat was introduced to be able to pass through the `--notes` option to\nthe revision machinery, we must now ensure that the `other_arg` array is\nappended at the end (the revision range must come before the pathspecs,\nif any).\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n Documentation/git-range-diff.txt |  4 ++\n builtin/range-diff.c             | 66 ++++++++++++++++++++++++--------\n range-diff.c                     |  2 +-\n t/t3206-range-diff.sh            | 13 ++++++-\n 4 files changed, 68 insertions(+), 17 deletions(-)\n\ndiff --git a/Documentation/git-range-diff.txt b/Documentation/git-range-diff.txt\nindex fe350d7f405..0b393715d70 100644\n--- a/Documentation/git-range-diff.txt\n+++ b/Documentation/git-range-diff.txt\n@@ -12,6 +12,7 @@ SYNOPSIS\n \t[--no-dual-color] [--creation-factor=<factor>]\n \t[--left-only | --right-only]\n \t( <range1> <range2> | <rev1>...<rev2> | <base> <rev1> <rev2> )\n+\t[[--] <path>...]\n \n DESCRIPTION\n -----------\n@@ -19,6 +20,9 @@ DESCRIPTION\n This command shows the differences between two versions of a patch\n series, or more generally, two commit ranges (ignoring merge commits).\n \n+In the presence of `<path>` arguments, these commit ranges are limited\n+accordingly.\n+\n To that end, it first finds pairs of commits from both commit ranges\n that correspond with each other. Two commits are said to correspond when\n the diff between their patches (i.e. the author information, the commit\ndiff --git a/builtin/range-diff.c b/builtin/range-diff.c\nindex 71319ed1d84..e2a74efb42a 100644\n--- a/builtin/range-diff.c\n+++ b/builtin/range-diff.c\n@@ -38,9 +38,10 @@ int cmd_range_diff(int argc, const char **argv, const char *prefix)\n \t\tOPT_END()\n \t};\n \tstruct option *options;\n-\tint res = 0;\n+\tint i, dash_dash = -1, res = 0;\n \tstruct strbuf range1 = STRBUF_INIT, range2 = STRBUF_INIT;\n \tstruct object_id oid;\n+\tconst char *three_dots = NULL;\n \n \tgit_config(git_diff_ui_config, NULL);\n \n@@ -48,7 +49,7 @@ int cmd_range_diff(int argc, const char **argv, const char *prefix)\n \n \toptions = parse_options_concat(range_diff_options, diffopt.parseopts);\n \targc = parse_options(argc, argv, prefix, options,\n-\t\t\t     builtin_range_diff_usage, 0);\n+\t\t\t     builtin_range_diff_usage, PARSE_OPT_KEEP_DASHDASH);\n \n \tdiff_setup_done(&diffopt);\n \n@@ -56,8 +57,20 @@ int cmd_range_diff(int argc, const char **argv, const char *prefix)\n \tif (!simple_color)\n \t\tdiffopt.use_color = 1;\n \n-\tif (argc == 3) {\n-\t\tif (get_oid_committish(argv[0], &oid))\n+\tfor (i = 0; i < argc; i++)\n+\t\tif (!strcmp(argv[i], \"--\")) {\n+\t\t\tdash_dash = i;\n+\t\t\tbreak;\n+\t\t}\n+\n+\tif (dash_dash == 3 ||\n+\t    (dash_dash < 0 && argc > 2 &&\n+\t     !get_oid_committish(argv[0], &oid) &&\n+\t     !get_oid_committish(argv[1], &oid) &&\n+\t     !get_oid_committish(argv[2], &oid))) {\n+\t\tif (dash_dash < 0)\n+\t\t\t; /* already validated arguments */\n+\t\telse if (get_oid_committish(argv[0], &oid))\n \t\t\tusage_msg_optf(_(\"not a revision: '%s'\"),\n \t\t\t\t       builtin_range_diff_usage, options,\n \t\t\t\t       argv[0]);\n@@ -72,8 +85,16 @@ int cmd_range_diff(int argc, const char **argv, const char *prefix)\n \n \t\tstrbuf_addf(&range1, \"%s..%s\", argv[0], argv[1]);\n \t\tstrbuf_addf(&range2, \"%s..%s\", argv[0], argv[2]);\n-\t} else if (argc == 2) {\n-\t\tif (!is_range_diff_range(argv[0]))\n+\n+\t\tstrvec_pushv(&other_arg, argv +\n+\t\t\t     (dash_dash < 0 ? 3 : dash_dash));\n+\t} else if (dash_dash == 2 ||\n+\t\t   (dash_dash < 0 && argc > 1 &&\n+\t\t    is_range_diff_range(argv[0]) &&\n+\t\t    is_range_diff_range(argv[1]))) {\n+\t\tif (dash_dash < 0)\n+\t\t\t; /* already validated arguments */\n+\t\telse if (!is_range_diff_range(argv[0]))\n \t\t\tusage_msg_optf(_(\"not a commit range: '%s'\"),\n \t\t\t\t       builtin_range_diff_usage, options,\n \t\t\t\t       argv[0]);\n@@ -84,25 +105,40 @@ int cmd_range_diff(int argc, const char **argv, const char *prefix)\n \n \t\tstrbuf_addstr(&range1, argv[0]);\n \t\tstrbuf_addstr(&range2, argv[1]);\n-\t} else if (argc == 1) {\n-\t\tconst char *b = strstr(argv[0], \"...\"), *a = argv[0];\n+\n+\t\tstrvec_pushv(&other_arg, argv +\n+\t\t\t     (dash_dash < 0 ? 2 : dash_dash));\n+\t} else if (dash_dash == 1 ||\n+\t\t   (dash_dash < 0 && argc > 0 &&\n+\t\t    (three_dots = strstr(argv[0], \"...\")))) {\n+\t\tconst char *a, *b;\n \t\tint a_len;\n \n-\t\tif (!b)\n+\t\tif (dash_dash < 0)\n+\t\t\t; /* already validated arguments */\n+\t\telse if (!(three_dots = strstr(argv[0], \"...\")))\n \t\t\tusage_msg_optf(_(\"not a symmetric range: '%s'\"),\n-\t\t\t\t       builtin_range_diff_usage, options,\n-\t\t\t\t       argv[0]);\n+\t\t\t\t\t builtin_range_diff_usage, options,\n+\t\t\t\t\t argv[0]);\n \n-\t\ta_len = (int)(b - a);\n-\t\tif (!a_len) {\n+\t\tif (three_dots == argv[0]) {\n \t\t\ta = \"HEAD\";\n \t\t\ta_len = strlen(a);\n+\t\t} else {\n+\t\t\ta = argv[0];\n+\t\t\ta_len = (int)(three_dots - a);\n \t\t}\n-\t\tb += 3;\n-\t\tif (!*b)\n+\n+\t\tif (three_dots[3])\n+\t\t\tb = three_dots + 3;\n+\t\telse\n \t\t\tb = \"HEAD\";\n+\n \t\tstrbuf_addf(&range1, \"%s..%.*s\", b, a_len, a);\n \t\tstrbuf_addf(&range2, \"%.*s..%s\", a_len, a, b);\n+\n+\t\tstrvec_pushv(&other_arg, argv +\n+\t\t\t     (dash_dash < 0 ? 1 : dash_dash));\n \t} else\n \t\tusage_msg_opt(_(\"need two commit ranges\"),\n \t\t\t      builtin_range_diff_usage, options);\ndiff --git a/range-diff.c b/range-diff.c\nindex f63b3ffc200..124dd678c38 100644\n--- a/range-diff.c\n+++ b/range-diff.c\n@@ -57,9 +57,9 @@ static int read_patches(const char *range, struct string_list *list,\n \t\t     \"--pretty=medium\",\n \t\t     \"--notes\",\n \t\t     NULL);\n+\tstrvec_push(&cp.args, range);\n \tif (other_arg)\n \t\tstrvec_pushv(&cp.args, other_arg->v);\n-\tstrvec_push(&cp.args, range);\n \tcp.out = -1;\n \tcp.no_stdin = 1;\n \tcp.git_cmd = 1;\ndiff --git a/t/t3206-range-diff.sh b/t/t3206-range-diff.sh\nindex d12e4e4cc6c..459beaf7d9c 100755\n--- a/t/t3206-range-diff.sh\n+++ b/t/t3206-range-diff.sh\n@@ -162,7 +162,7 @@ test_expect_success 'A^! and A^-<n> (unmodified)' '\n '\n \n test_expect_success 'A^{/..} is not mistaken for a range' '\n-\ttest_must_fail git range-diff topic^.. topic^{/..} 2>error &&\n+\ttest_must_fail git range-diff topic^.. topic^{/..} -- 2>error &&\n \ttest_i18ngrep \"not a commit range\" error\n '\n \n@@ -772,6 +772,17 @@ test_expect_success '--left-only/--right-only' '\n \ttest_cmp expect actual\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+\ttopic_oid=$(git rev-parse --short topic) &&\n+\tmode_change_oid=$(git rev-parse --short mode-only-change^) &&\n+\tfile_change_oid=$(git rev-parse --short mode-only-change) &&\n+\tgrep \"$mode_change_oid\" actual &&\n+\t! grep \"$file_change_oid\" actual &&\n+\t! grep \"$topic_oid\" actual\n+'\n+\n test_expect_success 'submodule changes are shown irrespective of diff.submodule' '\n \tgit init sub-repo &&\n \ttest_commit -C sub-repo sub-first &&\n-- \ngitgitgadget\n"},{"id":"462006","messageId":"xmqq8rnbymhd.fsf@gitster.g","threadId":"58351","inReplyTo":"4cd7f09557c7d0a4802b07d1e8440015e0a50524.1661506770.git.gitgitgadget@gmail.com","subject":"Re: [PATCH v2 2/3] range-diff: consistently validate the arguments","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2022-08-26T16:54:06Z","receivedAt":"2022-08-26T16:54:25Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"\"Johannes Schindelin via GitGitGadget\" <gitgitgadget@gmail.com>\nwrites:\n\n> From: Johannes Schindelin <johannes.schindelin@gmx.de>\n>\n> This patch lets `range-diff` validate the arguments not only when\n> invoked with one or two arguments, but also in the code path where three\n> arguments are handled.\n>\n> While at it, we now use `usage_msg_opt*()` consistently.\n>\n> Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n> ---\n>  builtin/range-diff.c | 41 +++++++++++++++++++++++++++++------------\n>  1 file changed, 29 insertions(+), 12 deletions(-)\n\nVery nice.  The updated series is worth having if only for this\nstep.  Very very pleasing.\n\n"},{"id":"462007","messageId":"xmqq1qt3ym3p.fsf@gitster.g","threadId":"58351","inReplyTo":"a52ad40e0155caf8285b86ec26bbd47cd2240466.1661506770.git.gitgitgadget@gmail.com","subject":"Re: [PATCH v2 3/3] range-diff: optionally accept pathspecs","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2022-08-26T17:02:18Z","receivedAt":"2022-08-26T17:02:46Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"\"Johannes Schindelin via GitGitGadget\" <gitgitgadget@gmail.com>\nwrites:\n\n> +\tfor (i = 0; i < argc; i++)\n> +\t\tif (!strcmp(argv[i], \"--\")) {\n> +\t\t\tdash_dash = i;\n> +\t\t\tbreak;\n> +\t\t}\n> +\n> +\tif (dash_dash == 3 ||\n> +\t    (dash_dash < 0 && argc > 2 &&\n> +\t     !get_oid_committish(argv[0], &oid) &&\n> +\t     !get_oid_committish(argv[1], &oid) &&\n> +\t     !get_oid_committish(argv[2], &oid))) {\n> +\t\tif (dash_dash < 0)\n> +\t\t\t; /* already validated arguments */\n> +\t\telse if (get_oid_committish(argv[0], &oid))\n>  \t\t\tusage_msg_optf(_(\"not a revision: '%s'\"),\n>  \t\t\t\t       builtin_range_diff_usage, options,\n>  \t\t\t\t       argv[0]);\n\nVery reasonable.  We won't ever have \"--\" on the command line as\nanything but a dash-dash (i.e. there is no option to the command\nthat can take any string), and thanks to the previous step, this\naddition falls in place very naturally.  Very nicely done.\n\nWill replace.  I _think_ this is ready for 'next', but let's see if\nothers spot problems for a few days.\n\nTHanks.\n"}]}