parse-options: introduce precision handling for `OPTION_UNSIGNED`

This commit is the equivalent to the preceding commit, but instead of introducing precision handling for `OPTION_INTEGER` we introduce it for `OPTION_UNSIGNED`. Signed-off-by: Patrick Steinhardt <ps@pks.im> Signed-off-by: Junio C Hamano <gitster@pobox.com>

Patrick Steinhardt committed Apr 17, 2025 at 12:49 UTC bc288c59298f199348418ca08322046c67c9a0a2
6 files changed +60 -13
parse-options.c
+37 -11
@@ -197,7 +197,7 @@ static enum parse_opt_result do_get_value(struct parse_opt_ctx_t *p,
197
198 if (value < lower_bound)
199 return error(_("value %s for %s not in range [%"PRIdMAX",%"PRIdMAX"]"),
200 - arg, optname(opt, flags), lower_bound, upper_bound);
200 + arg, optname(opt, flags), (intmax_t)lower_bound, (intmax_t)upper_bound);
201
202 switch (opt->precision) {
203 case 1:
@@ -218,21 +218,47 @@ static enum parse_opt_result do_get_value(struct parse_opt_ctx_t *p,
218 }
219 }
220 case OPTION_UNSIGNED:
221 + {
222 + uintmax_t upper_bound = UINTMAX_MAX >> (bitsizeof(uintmax_t) - CHAR_BIT * opt->precision);
223 + uintmax_t value;
224 +
225 if (unset) {
222 - *(unsigned long *)opt->value = 0;
223 - return 0;
224 - }
225 - if (opt->flags & PARSE_OPT_OPTARG && !p->opt) {
226 - *(unsigned long *)opt->value = opt->defval;
227 - return 0;
228 - }
229 - if (get_arg(p, opt, flags, &arg))
226 + value = 0;
227 + } else if (opt->flags & PARSE_OPT_OPTARG && !p->opt) {
228 + value = opt->defval;
229 + } else if (get_arg(p, opt, flags, &arg)) {
230 return -1;
231 - if (!git_parse_ulong(arg, opt->value))
231 + } else if (!*arg) {
232 + return error(_("%s expects a numerical value"),
233 + optname(opt, flags));
234 + } else if (!git_parse_unsigned(arg, &value, upper_bound)) {
235 + if (errno == ERANGE)
236 + return error(_("value %s for %s not in range [%"PRIdMAX",%"PRIdMAX"]"),
237 + arg, optname(opt, flags), (uintmax_t) 0, upper_bound);
238 +
239 return error(_("%s expects a non-negative integer value"
240 " with an optional k/m/g suffix"),
241 optname(opt, flags));
235 - return 0;
242 + }
243 +
244 + switch (opt->precision) {
245 + case 1:
246 + *(uint8_t *)opt->value = value;
247 + return 0;
248 + case 2:
249 + *(uint16_t *)opt->value = value;
250 + return 0;
251 + case 4:
252 + *(uint32_t *)opt->value = value;
253 + return 0;
254 + case 8:
255 + *(uint64_t *)opt->value = value;
256 + return 0;
257 + default:
258 + BUG("invalid precision for option %s",
259 + optname(opt, flags));
260 + }
261 + }
262
263 default:
264 BUG("opt->type %d should not happen", opt->type);
parse-options.h
+1
@@ -281,6 +281,7 @@ struct option {
281 .short_name = (s), \
282 .long_name = (l), \
283 .value = (v), \
284 + .precision = sizeof(*v), \
285 .argh = N_("n"), \
286 .help = (h), \
287 .flags = PARSE_OPT_NONEG, \
parse.c
+1 -1
@@ -51,7 +51,7 @@ int git_parse_signed(const char *value, intmax_t *ret, intmax_t max)
51 return 0;
52 }
53
54 -static int git_parse_unsigned(const char *value, uintmax_t *ret, uintmax_t max)
54 +int git_parse_unsigned(const char *value, uintmax_t *ret, uintmax_t max)
55 {
56 if (value && *value) {
57 char *end;
parse.h
+1
@@ -2,6 +2,7 @@
2 #define PARSE_H
3
4 int git_parse_signed(const char *value, intmax_t *ret, intmax_t max);
5 +int git_parse_unsigned(const char *value, uintmax_t *ret, uintmax_t max);
6 int git_parse_ssize_t(const char *, ssize_t *);
7 int git_parse_ulong(const char *, unsigned long *);
8 int git_parse_int(const char *value, int *ret);
t/helper/test-parse-options.c
+3
@@ -120,6 +120,7 @@ int cmd__parse_options(int argc, const char **argv)
120 };
121 struct string_list expect = STRING_LIST_INIT_NODUP;
122 struct string_list list = STRING_LIST_INIT_NODUP;
123 + uint16_t u16 = 0;
124 int16_t i16 = 0;
125
126 struct option options[] = {
@@ -143,6 +144,7 @@ int cmd__parse_options(int argc, const char **argv)
144 OPT_INTEGER(0, "i16", &i16, "get a 16 bit integer"),
145 OPT_INTEGER('j', NULL, &integer, "get a integer, too"),
146 OPT_UNSIGNED('u', "unsigned", &unsigned_integer, "get an unsigned integer"),
147 + OPT_UNSIGNED(0, "u16", &u16, "get a 16 bit unsigned integer"),
148 OPT_SET_INT(0, "set23", &integer, "set integer to 23", 23),
149 OPT_CMDMODE(0, "mode1", &integer, "set integer to 1 (cmdmode option)", 1),
150 OPT_CMDMODE(0, "mode2", &integer, "set integer to 2 (cmdmode option)", 2),
@@ -214,6 +216,7 @@ int cmd__parse_options(int argc, const char **argv)
216 show(&expect, &ret, "integer: %d", integer);
217 show(&expect, &ret, "i16: %"PRIdMAX, (intmax_t) i16);
218 show(&expect, &ret, "unsigned: %lu", unsigned_integer);
219 + show(&expect, &ret, "u16: %"PRIuMAX, (uintmax_t) u16);
220 show(&expect, &ret, "timestamp: %"PRItime, timestamp);
221 show(&expect, &ret, "string: %s", string ? string : "(not set)");
222 show(&expect, &ret, "abbrev: %d", abbrev);
t/t0040-parse-options.sh
+17 -1
@@ -25,6 +25,7 @@ usage: test-tool parse-options <options>
25 --[no-]i16 <n> get a 16 bit integer
26 -j <n> get a integer, too
27 -u, --unsigned <n> get an unsigned integer
28 + --u16 <n> get a 16 bit unsigned integer
29 --[no-]set23 set integer to 23
30 --mode1 set integer to 1 (cmdmode option)
31 --mode2 set integer to 2 (cmdmode option)
@@ -141,6 +142,7 @@ boolean: 2
142 integer: 1729
143 i16: 0
144 unsigned: 16384
145 +u16: 0
146 timestamp: 0
147 string: 123
148 abbrev: 7
@@ -162,6 +164,7 @@ boolean: 2
164 integer: 1729
165 i16: 9000
166 unsigned: 16384
167 +u16: 32768
168 timestamp: 0
169 string: 321
170 abbrev: 10
@@ -173,7 +176,7 @@ EOF
176
177 test_expect_success 'long options' '
178 test-tool parse-options --boolean --integer 1729 --i16 9000 --unsigned 16k \
176 - --boolean --string2=321 --verbose --verbose --no-dry-run \
179 + --u16 32k --boolean --string2=321 --verbose --verbose --no-dry-run \
180 --abbrev=10 --file fi.le --obsolete \
181 >output 2>output.err &&
182 test_must_be_empty output.err &&
@@ -186,6 +189,7 @@ test_expect_success 'abbreviate to something longer than SHA1 length' '
189 integer: 0
190 i16: 0
191 unsigned: 0
192 + u16: 0
193 timestamp: 0
194 string: (not set)
195 abbrev: 100
@@ -261,6 +265,7 @@ boolean: 1
265 integer: 13
266 i16: 0
267 unsigned: 0
268 +u16: 0
269 timestamp: 0
270 string: 123
271 abbrev: 7
@@ -285,6 +290,7 @@ boolean: 0
290 integer: 2
291 i16: 0
292 unsigned: 0
293 +u16: 0
294 timestamp: 0
295 string: (not set)
296 abbrev: 7
@@ -353,6 +359,7 @@ boolean: 5
359 integer: 4
360 i16: 0
361 unsigned: 0
362 +u16: 0
363 timestamp: 0
364 string: (not set)
365 abbrev: 7
@@ -379,6 +386,7 @@ boolean: 1
386 integer: 23
387 i16: 0
388 unsigned: 0
389 +u16: 0
390 timestamp: 0
391 string: (not set)
392 abbrev: 7
@@ -459,6 +467,7 @@ boolean: 0
467 integer: 0
468 i16: 0
469 unsigned: 0
470 +u16: 0
471 timestamp: 0
472 string: (not set)
473 abbrev: 7
@@ -806,4 +815,11 @@ test_expect_success 'i16 limits range' '
815 test_grep "value -32769 for option .i16. not in range \[-32768,32767\]" err
816 '
817
818 +test_expect_success 'u16 limits range' '
819 + test-tool parse-options --u16 65535 >out &&
820 + test_grep "u16: 65535" out &&
821 + test_must_fail test-tool parse-options --u16 65536 2>err &&
822 + test_grep "value 65536 for option .u16. not in range \[0,65535\]" err
823 +'
824 +
825 test_done