Skip to content

Commit b757478

Browse files
dschogitster
authored andcommitted
range-diff: optionally accept pathspecs
The `git range-diff` command can be quite expensive, which is not a surprise given that the underlying algorithm to match up pairs of commits between the provided two commit ranges has a cubic runtime. Therefore it makes sense to restrict the commit ranges as much as possible, to reduce the amount of input to that O(N^3) algorithm. In chatty repositories with wide trees, this is not necessarily possible merely by choosing commit ranges wisely. Let's give users another option to restrict the commit ranges: by providing a pathspec. That helps in repositories with wide trees because it is likely that the user has a good idea which subset of the tree they are actually interested in. Example: git range-diff upstream/main upstream/seen HEAD -- range-diff.c This shows commits that are either in the local branch or in `seen`, but not in `main`, skipping all commits that do not touch `range-diff.c`. Note: Since we piggy-back the pathspecs onto the `other_arg` mechanism that was introduced to be able to pass through the `--notes` option to the revision machinery, we must now ensure that the `other_arg` array is appended at the end (the revision range must come before the pathspecs, if any). Signed-off-by: Johannes Schindelin <[email protected]> Signed-off-by: Junio C Hamano <[email protected]>
1 parent 0087d7d commit b757478

File tree

4 files changed

+68
-17
lines changed

4 files changed

+68
-17
lines changed

Documentation/git-range-diff.txt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,13 +12,17 @@ SYNOPSIS
1212
[--no-dual-color] [--creation-factor=<factor>]
1313
[--left-only | --right-only]
1414
( <range1> <range2> | <rev1>...<rev2> | <base> <rev1> <rev2> )
15+
[[--] <path>...]
1516

1617
DESCRIPTION
1718
-----------
1819

1920
This command shows the differences between two versions of a patch
2021
series, or more generally, two commit ranges (ignoring merge commits).
2122

23+
In the presence of `<path>` arguments, these commit ranges are limited
24+
accordingly.
25+
2226
To that end, it first finds pairs of commits from both commit ranges
2327
that correspond with each other. Two commits are said to correspond when
2428
the diff between their patches (i.e. the author information, the commit

builtin/range-diff.c

Lines changed: 51 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -38,26 +38,39 @@ int cmd_range_diff(int argc, const char **argv, const char *prefix)
3838
OPT_END()
3939
};
4040
struct option *options;
41-
int res = 0;
41+
int i, dash_dash = -1, res = 0;
4242
struct strbuf range1 = STRBUF_INIT, range2 = STRBUF_INIT;
4343
struct object_id oid;
44+
const char *three_dots = NULL;
4445

4546
git_config(git_diff_ui_config, NULL);
4647

4748
repo_diff_setup(the_repository, &diffopt);
4849

4950
options = parse_options_concat(range_diff_options, diffopt.parseopts);
5051
argc = parse_options(argc, argv, prefix, options,
51-
builtin_range_diff_usage, 0);
52+
builtin_range_diff_usage, PARSE_OPT_KEEP_DASHDASH);
5253

5354
diff_setup_done(&diffopt);
5455

5556
/* force color when --dual-color was used */
5657
if (!simple_color)
5758
diffopt.use_color = 1;
5859

59-
if (argc == 3) {
60-
if (get_oid_committish(argv[0], &oid))
60+
for (i = 0; i < argc; i++)
61+
if (!strcmp(argv[i], "--")) {
62+
dash_dash = i;
63+
break;
64+
}
65+
66+
if (dash_dash == 3 ||
67+
(dash_dash < 0 && argc > 2 &&
68+
!get_oid_committish(argv[0], &oid) &&
69+
!get_oid_committish(argv[1], &oid) &&
70+
!get_oid_committish(argv[2], &oid))) {
71+
if (dash_dash < 0)
72+
; /* already validated arguments */
73+
else if (get_oid_committish(argv[0], &oid))
6174
usage_msg_optf(_("not a revision: '%s'"),
6275
builtin_range_diff_usage, options,
6376
argv[0]);
@@ -72,8 +85,16 @@ int cmd_range_diff(int argc, const char **argv, const char *prefix)
7285

7386
strbuf_addf(&range1, "%s..%s", argv[0], argv[1]);
7487
strbuf_addf(&range2, "%s..%s", argv[0], argv[2]);
75-
} else if (argc == 2) {
76-
if (!is_range_diff_range(argv[0]))
88+
89+
strvec_pushv(&other_arg, argv +
90+
(dash_dash < 0 ? 3 : dash_dash));
91+
} else if (dash_dash == 2 ||
92+
(dash_dash < 0 && argc > 1 &&
93+
is_range_diff_range(argv[0]) &&
94+
is_range_diff_range(argv[1]))) {
95+
if (dash_dash < 0)
96+
; /* already validated arguments */
97+
else if (!is_range_diff_range(argv[0]))
7798
usage_msg_optf(_("not a commit range: '%s'"),
7899
builtin_range_diff_usage, options,
79100
argv[0]);
@@ -84,25 +105,40 @@ int cmd_range_diff(int argc, const char **argv, const char *prefix)
84105

85106
strbuf_addstr(&range1, argv[0]);
86107
strbuf_addstr(&range2, argv[1]);
87-
} else if (argc == 1) {
88-
const char *b = strstr(argv[0], "..."), *a = argv[0];
108+
109+
strvec_pushv(&other_arg, argv +
110+
(dash_dash < 0 ? 2 : dash_dash));
111+
} else if (dash_dash == 1 ||
112+
(dash_dash < 0 && argc > 0 &&
113+
(three_dots = strstr(argv[0], "...")))) {
114+
const char *a, *b;
89115
int a_len;
90116

91-
if (!b)
117+
if (dash_dash < 0)
118+
; /* already validated arguments */
119+
else if (!(three_dots = strstr(argv[0], "...")))
92120
usage_msg_optf(_("not a symmetric range: '%s'"),
93-
builtin_range_diff_usage, options,
94-
argv[0]);
121+
builtin_range_diff_usage, options,
122+
argv[0]);
95123

96-
a_len = (int)(b - a);
97-
if (!a_len) {
124+
if (three_dots == argv[0]) {
98125
a = "HEAD";
99126
a_len = strlen(a);
127+
} else {
128+
a = argv[0];
129+
a_len = (int)(three_dots - a);
100130
}
101-
b += 3;
102-
if (!*b)
131+
132+
if (three_dots[3])
133+
b = three_dots + 3;
134+
else
103135
b = "HEAD";
136+
104137
strbuf_addf(&range1, "%s..%.*s", b, a_len, a);
105138
strbuf_addf(&range2, "%.*s..%s", a_len, a, b);
139+
140+
strvec_pushv(&other_arg, argv +
141+
(dash_dash < 0 ? 1 : dash_dash));
106142
} else
107143
usage_msg_opt(_("need two commit ranges"),
108144
builtin_range_diff_usage, options);

range-diff.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,9 +57,9 @@ static int read_patches(const char *range, struct string_list *list,
5757
"--pretty=medium",
5858
"--notes",
5959
NULL);
60+
strvec_push(&cp.args, range);
6061
if (other_arg)
6162
strvec_pushv(&cp.args, other_arg->v);
62-
strvec_push(&cp.args, range);
6363
cp.out = -1;
6464
cp.no_stdin = 1;
6565
cp.git_cmd = 1;

t/t3206-range-diff.sh

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -162,7 +162,7 @@ test_expect_success 'A^! and A^-<n> (unmodified)' '
162162
'
163163

164164
test_expect_success 'A^{/..} is not mistaken for a range' '
165-
test_must_fail git range-diff topic^.. topic^{/..} 2>error &&
165+
test_must_fail git range-diff topic^.. topic^{/..} -- 2>error &&
166166
test_i18ngrep "not a commit range" error
167167
'
168168

@@ -772,6 +772,17 @@ test_expect_success '--left-only/--right-only' '
772772
test_cmp expect actual
773773
'
774774

775+
test_expect_success 'ranges with pathspecs' '
776+
git range-diff topic...mode-only-change -- other-file >actual &&
777+
test_line_count = 2 actual &&
778+
topic_oid=$(git rev-parse --short topic) &&
779+
mode_change_oid=$(git rev-parse --short mode-only-change^) &&
780+
file_change_oid=$(git rev-parse --short mode-only-change) &&
781+
grep "$mode_change_oid" actual &&
782+
! grep "$file_change_oid" actual &&
783+
! grep "$topic_oid" actual
784+
'
785+
775786
test_expect_success 'submodule changes are shown irrespective of diff.submodule' '
776787
git init sub-repo &&
777788
test_commit -C sub-repo sub-first &&

0 commit comments

Comments
 (0)