Skip to content

Commit 71a1e94

Browse files
committed
revision: parse integer arguments to --max-count, --skip, etc., more carefully
The "rev-list" and other commands in the "log" family, being the oldest part of the system, use their own custom argument parsers, and integer values of some options are parsed with atoi(), which allows a non-digit after the number (e.g., "1q") to be silently ignored. As a natural consequence, an argument that does not begin with a digit (e.g., "q") silently becomes zero, too. Switch to use strtol_i() and parse_timestamp() appropriately to catch bogus input. Note that one may naïvely expect that --max-count, --skip, etc., to only take non-negative values, but we must allow them to also take negative values, as an escape hatch to countermand a limit set by an earlier option on the command line; the underlying variables are initialized to (-1) and "--max-count=-1", for example, is a legitimate way to reinitialize the limit. Signed-off-by: Junio C Hamano <[email protected]>
1 parent 61a22dd commit 71a1e94

File tree

3 files changed

+57
-13
lines changed

3 files changed

+57
-13
lines changed

revision.c

Lines changed: 30 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -2214,6 +2214,27 @@ static void add_message_grep(struct rev_info *revs, const char *pattern)
22142214
add_grep(revs, pattern, GREP_PATTERN_BODY);
22152215
}
22162216

2217+
static int parse_count(const char *arg)
2218+
{
2219+
int count;
2220+
2221+
if (strtol_i(arg, 10, &count) < 0)
2222+
die("'%s': not an integer", arg);
2223+
return count;
2224+
}
2225+
2226+
static timestamp_t parse_age(const char *arg)
2227+
{
2228+
timestamp_t num;
2229+
char *p;
2230+
2231+
errno = 0;
2232+
num = parse_timestamp(arg, &p, 10);
2233+
if (errno || *p || p == arg)
2234+
die("'%s': not a number of seconds since epoch", arg);
2235+
return num;
2236+
}
2237+
22172238
static int handle_revision_opt(struct rev_info *revs, int argc, const char **argv,
22182239
int *unkc, const char **unkv,
22192240
const struct setup_revision_opt* opt)
@@ -2240,29 +2261,27 @@ static int handle_revision_opt(struct rev_info *revs, int argc, const char **arg
22402261
}
22412262

22422263
if ((argcount = parse_long_opt("max-count", argv, &optarg))) {
2243-
revs->max_count = atoi(optarg);
2264+
revs->max_count = parse_count(optarg);
22442265
revs->no_walk = 0;
22452266
return argcount;
22462267
} else if ((argcount = parse_long_opt("skip", argv, &optarg))) {
2247-
revs->skip_count = atoi(optarg);
2268+
revs->skip_count = parse_count(optarg);
22482269
return argcount;
22492270
} else if ((*arg == '-') && isdigit(arg[1])) {
22502271
/* accept -<digit>, like traditional "head" */
2251-
if (strtol_i(arg + 1, 10, &revs->max_count) < 0 ||
2252-
revs->max_count < 0)
2253-
die("'%s': not a non-negative integer", arg + 1);
2272+
revs->max_count = parse_count(arg + 1);
22542273
revs->no_walk = 0;
22552274
} else if (!strcmp(arg, "-n")) {
22562275
if (argc <= 1)
22572276
return error("-n requires an argument");
2258-
revs->max_count = atoi(argv[1]);
2277+
revs->max_count = parse_count(argv[1]);
22592278
revs->no_walk = 0;
22602279
return 2;
22612280
} else if (skip_prefix(arg, "-n", &optarg)) {
2262-
revs->max_count = atoi(optarg);
2281+
revs->max_count = parse_count(optarg);
22632282
revs->no_walk = 0;
22642283
} else if ((argcount = parse_long_opt("max-age", argv, &optarg))) {
2265-
revs->max_age = atoi(optarg);
2284+
revs->max_age = parse_age(optarg);
22662285
return argcount;
22672286
} else if ((argcount = parse_long_opt("since", argv, &optarg))) {
22682287
revs->max_age = approxidate(optarg);
@@ -2274,7 +2293,7 @@ static int handle_revision_opt(struct rev_info *revs, int argc, const char **arg
22742293
revs->max_age = approxidate(optarg);
22752294
return argcount;
22762295
} else if ((argcount = parse_long_opt("min-age", argv, &optarg))) {
2277-
revs->min_age = atoi(optarg);
2296+
revs->min_age = parse_age(optarg);
22782297
return argcount;
22792298
} else if ((argcount = parse_long_opt("before", argv, &optarg))) {
22802299
revs->min_age = approxidate(optarg);
@@ -2362,11 +2381,11 @@ static int handle_revision_opt(struct rev_info *revs, int argc, const char **arg
23622381
} else if (!strcmp(arg, "--no-merges")) {
23632382
revs->max_parents = 1;
23642383
} else if (skip_prefix(arg, "--min-parents=", &optarg)) {
2365-
revs->min_parents = atoi(optarg);
2384+
revs->min_parents = parse_count(optarg);
23662385
} else if (!strcmp(arg, "--no-min-parents")) {
23672386
revs->min_parents = 0;
23682387
} else if (skip_prefix(arg, "--max-parents=", &optarg)) {
2369-
revs->max_parents = atoi(optarg);
2388+
revs->max_parents = parse_count(optarg);
23702389
} else if (!strcmp(arg, "--no-max-parents")) {
23712390
revs->max_parents = -1;
23722391
} else if (!strcmp(arg, "--boundary")) {

t/t6005-rev-list-count.sh

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,20 +18,34 @@ test_expect_success 'no options' '
1818
'
1919

2020
test_expect_success '--max-count' '
21+
test_must_fail git rev-list --max-count=1q HEAD 2>error &&
22+
grep "not an integer" error &&
23+
2124
test_stdout_line_count = 0 git rev-list HEAD --max-count=0 &&
2225
test_stdout_line_count = 3 git rev-list HEAD --max-count=3 &&
2326
test_stdout_line_count = 5 git rev-list HEAD --max-count=5 &&
24-
test_stdout_line_count = 5 git rev-list HEAD --max-count=10
27+
test_stdout_line_count = 5 git rev-list HEAD --max-count=10 &&
28+
test_stdout_line_count = 5 git rev-list HEAD --max-count=-1
2529
'
2630

2731
test_expect_success '--max-count all forms' '
32+
test_must_fail git rev-list -1q HEAD 2>error &&
33+
grep "not an integer" error &&
34+
test_must_fail git rev-list --1 HEAD &&
35+
test_must_fail git rev-list -n 1q HEAD 2>error &&
36+
grep "not an integer" error &&
37+
2838
test_stdout_line_count = 1 git rev-list HEAD --max-count=1 &&
2939
test_stdout_line_count = 1 git rev-list HEAD -1 &&
3040
test_stdout_line_count = 1 git rev-list HEAD -n1 &&
31-
test_stdout_line_count = 1 git rev-list HEAD -n 1
41+
test_stdout_line_count = 1 git rev-list HEAD -n 1 &&
42+
test_stdout_line_count = 5 git rev-list HEAD -n -1
3243
'
3344

3445
test_expect_success '--skip' '
46+
test_must_fail git rev-list --skip 1q HEAD 2>error &&
47+
grep "not an integer" error &&
48+
3549
test_stdout_line_count = 5 git rev-list HEAD --skip=0 &&
3650
test_stdout_line_count = 2 git rev-list HEAD --skip=3 &&
3751
test_stdout_line_count = 0 git rev-list HEAD --skip=5 &&

t/t6009-rev-list-parent.sh

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,17 @@ test_expect_success 'setup roots, merges and octopuses' '
6262
git checkout main
6363
'
6464

65+
test_expect_success 'parse --max-parents & --min-parents' '
66+
test_must_fail git rev-list --max-parents=1q HEAD 2>error &&
67+
grep "not an integer" error &&
68+
69+
test_must_fail git rev-list --min-parents=1q HEAD 2>error &&
70+
grep "not an integer" error &&
71+
72+
git rev-list --max-parents=1 --min-parents=1 HEAD &&
73+
git rev-list --max-parents=-1 --min-parents=-1 HEAD
74+
'
75+
6576
test_expect_success 'rev-list roots' '
6677
6778
check_revlist "--max-parents=0" one five

0 commit comments

Comments
 (0)