parse-options: detect ambiguous self-negation

Git currently does not detect the ambiguity of an option that starts with "no" like --notes and its negated form if given just --n or --no. All Git commands with such options have other negatable options, and we detect the ambiguity with them, so that's currently only a potential problem for scripts that use git rev-parse --parseopt. Let's fix it nevertheless, as there's no need for that confusion. To detect the ambiguity we have to loosen the check in register_abbrev(), as an option is considered an alias of itself. Add non-matching negation flags as a criterion to recognize an option being ambiguous with its negated form. And we need to keep going after finding a non-negated option as an abbreviated candidate and perform the negation checks in the same loop. Signed-off-by: René Scharfe <l.s.r@web.de> Signed-off-by: Junio C Hamano <gitster@pobox.com>

René Scharfe committed Mar 3, 2024 at 13:19 UTC 0d8a3097c74ed3b78442c4d533f3427316596a46
2 files changed +12 -2
parse-options.c
+1 -2
@@ -363,7 +363,7 @@ static void register_abbrev(struct parse_opt_ctx_t *p,
363 if (p->flags & PARSE_OPT_KEEP_UNKNOWN_OPT)
364 return;
365 if (abbrev->option &&
366 - !is_alias(p, abbrev->option, option)) {
366 + !(abbrev->flags == flags && is_alias(p, abbrev->option, option))) {
367 /*
368 * If this is abbreviated, it is
369 * ambiguous. So when there is no
@@ -406,7 +406,6 @@ static enum parse_opt_result parse_long_opt(
406 if (!strncmp(long_name, arg, arg_end - arg)) {
407 register_abbrev(p, options, flags ^ opt_flags,
408 &abbrev, &ambiguous);
409 - continue;
409 }
410 /* negation allowed? */
411 if (options->flags & PARSE_OPT_NONEG)
t/t1502-rev-parse-parseopt.sh
+11
@@ -322,4 +322,15 @@ check_invalid_long_option optionspec-neg --no-positive-only
322 check_invalid_long_option optionspec-neg --negative
323 check_invalid_long_option optionspec-neg --no-no-negative
324
325 +test_expect_success 'ambiguous: --no matches both --noble and --no-noble' '
326 + cat >spec <<-\EOF &&
327 + some-command [options]
328 + --
329 + noble The feudal switch.
330 + EOF
331 + test_expect_code 129 env GIT_TEST_DISALLOW_ABBREVIATED_OPTIONS=false \
332 + git rev-parse --parseopt -- <spec 2>err --no &&
333 + grep "error: ambiguous option: no (could be --noble or --no-noble)" err
334 +'
335 +
336 test_done