git/list[1] front-page[2] threads[3] people[4] search[5] about
 

Re: [PATCH v3 2/4] parse-options: extract lookup_long_opt() from parse_long_opt()

From
Kaartic Sivaraam <kaartic.sivaraam@gmail.com>
Date
Oct 30, 2026, 21:08 UTC
Message-ID
<02ba1b9d-b598-454b-897f-cb33ba190b13@gmail.com>
In-Reply-To
<20261012074413.337183-3-christian.couder@gmail.com>
On 10/12/26 13:14, Christian Couder wrote:
Show 16 quoted lines
> In a following commit, an early scan of the command line will need to
> look up long options the same way parse_options() does, including
> unambiguous abbreviations and negated forms, but without any side
> effect: it must neither set option values nor call die() or error().
> > parse_long_opt() can't be used for that, as it does both the look up
> and the actions that follow it: it calls get_value() on the option it
> finds, die()s when abbreviations are disallowed, and error()s on an
> ambiguous abbreviation.
> > Let's extract a new lookup_long_opt() helper from parse_long_opt() to
> perform only the look up part, and let parse_long_opt() act on what
> lookup_long_opt() returns.
> > There is no behavior change intended. In particular an exact match is
> still preferred to any abbreviation, abbreviations are still rejected
> when disallowed only if there is no exact match, and the ambiguity
> error message is unchanged.
>
Neatly explained!
Show 101 quoted lines
> diff --git a/parse-options.c b/parse-options.c
> index d7cbb5a343..a98b958423 100644
> --- a/parse-options.c
> +++ b/parse-options.c
> @@ -516,16 +516,46 @@ static void register_abbrev(struct parse_opt_ctx_t *p,
>   	abbrev->flags = flags;
>   }
>   > -static enum parse_opt_result parse_long_opt(
> -	struct parse_opt_ctx_t *p, const char *arg,
> -	const struct option *options)
> +enum long_opt_lookup {
> +	LONG_OPT_EXACT,      /* exact match found */
> +	LONG_OPT_ABBREV,     /* unambiguous abbreviation found */
> +	LONG_OPT_AMBIGUOUS,  /* more than one candidate abbreviation */
> +	LONG_OPT_UNKNOWN,    /* nothing matches */
> +};
> +
> +/*
> + * Look up the long option named by `arg` (without its leading "--")
> + * without acting on it. (Neither prints nor dies.)
> + *
> + * Return value:
> + * - On LONG_OPT_EXACT, `found` is the exact matching option and its flags
> + *   (including OPT_UNSET for a negated form), and `stuck` points after
> + *   the '=' or is NULL.
> + * - On LONG_OPT_ABBREV, `found` is the unambiguous abbreviation, same as
> + *   above for its flags and `stuck`.
> + * - On LONG_OPT_AMBIGUOUS, `found` and `other` are two candidates of an
> + *   ambiguous abbreviation.
> + * - On LONG_OPT_UNKNOWN, nothing matches, no result.
> + *
> + * `other` is only meaningful for LONG_OPT_AMBIGUOUS.
> + */
> +static enum long_opt_lookup lookup_long_opt(struct parse_opt_ctx_t *p,
> +					    const char *arg,
> +					    const struct option *options,
> +					    struct parsed_option *found,
> +					    struct parsed_option *other,
> +					    const char **stuck)
>   {
> +	enum opt_parsed flags = OPT_LONG;
>   	const char *arg_end = strchrnul(arg, '=');
>   	const char *arg_start = arg;
> -	enum opt_parsed flags = OPT_LONG;
>   	int arg_starts_with_no_no = 0;
> -	struct parsed_option abbrev = { .option = NULL, .flags = OPT_LONG };
> -	struct parsed_option ambiguous = { .option = NULL, .flags = OPT_LONG };
> +
> +	found->option = NULL;
> +	found->flags = OPT_LONG;
> +	other->option = NULL;
> +	other->flags = OPT_LONG;
> +	*stuck = NULL;
>   >   	if (skip_prefix(arg_start, "no-", &arg_start)) {
>   		if (skip_prefix(arg_start, "no-", &arg_start))
> @@ -554,43 +584,70 @@ static enum parse_opt_result parse_long_opt(
>   >   		if (skip_prefix(arg_start, long_name, &rest)) {
>   			if (*rest == '=')
> -				p->opt = rest + 1;
> +				*stuck = rest + 1;
>   			else if (*rest)
>   				continue;
> -			return get_value(p, options, flags ^ opt_flags);
> +			found->option = options;
> +			found->flags = flags ^ opt_flags;
> +			return LONG_OPT_EXACT;
>   		}
>   >   		/* abbreviated? */
>   		if (!strncmp(long_name, arg_start, arg_end - arg_start))
>   			register_abbrev(p, options, flags ^ opt_flags,
> -					&abbrev, &ambiguous);
> +					found, other);
>   >   		/* negated and abbreviated very much? */
>   		if (allow_unset && starts_with("no-", arg))
>   			register_abbrev(p, options, OPT_UNSET ^ opt_flags,
> -					&abbrev, &ambiguous);
> +					found, other);
>   	}
>   > -	if (disallow_abbreviated_options && (ambiguous.option || abbrev.option))
> +	if (other->option)
> +		return LONG_OPT_AMBIGUOUS;
> +
> +	if (found->option) {
> +		if (*arg_end)
> +			*stuck = arg_end + 1;
> +		return LONG_OPT_ABBREV;
> +	}
> +
> +	return LONG_OPT_UNKNOWN;
> +}
> +
> +static enum parse_opt_result parse_long_opt(struct parse_opt_ctx_t *p,
> +					    const char *arg,
> +					    const struct option *options)
> +{
> +	struct parsed_option found;
> +	struct parsed_option other;
> +	const char *stuck;
> +	enum long_opt_lookup result = lookup_long_opt(p, arg, options,
> +						      &found, &other, &stuck);
> +
Would it not be better to consume the `enum long_op_lookup` return value via a switch rather than having a set of if statements to cover all the cases?
Show 37 quoted lines
> +	if (result == LONG_OPT_UNKNOWN)
> +		return PARSE_OPT_UNKNOWN;
> +
> +	if (disallow_abbreviated_options &&
> +	    (result == LONG_OPT_ABBREV || result == LONG_OPT_AMBIGUOUS)) {
> +		const char *arg_end = strchrnul(arg, '=');
>   		die("disallowed abbreviated or ambiguous option '%.*s'",
>   		    (int)(arg_end - arg), arg);
> +	}
>   > -	if (ambiguous.option) {
> -		error(_("ambiguous option: %s "
> -			"(could be --%s%s or --%s%s)"),
> -			arg,
> -			(ambiguous.flags & OPT_UNSET) ?  "no-" : "",
> -			ambiguous.option->long_name,
> -			(abbrev.flags & OPT_UNSET) ?  "no-" : "",
> -			abbrev.option->long_name);
> +	if (result == LONG_OPT_AMBIGUOUS) {
> +		error(_("ambiguous option: %s (could be --%s%s or --%s%s)"), arg,
> +		      (other.flags & OPT_UNSET) ? "no-" : "", other.option->long_name,
> +		      (found.flags & OPT_UNSET) ? "no-" : "", found.option->long_name);
>   		return PARSE_OPT_HELP_ERROR;
>   	}
> -	if (abbrev.option) {
> -		if (*arg_end)
> -			p->opt = arg_end + 1;
> -		return get_value(p, abbrev.option, abbrev.flags);
> +
> +	if (result == LONG_OPT_ABBREV || result == LONG_OPT_EXACT) {
> +		p->opt = stuck;
> +		return get_value(p, found.option, found.flags);
>   	}
> -	return PARSE_OPT_UNKNOWN;
> +
> +	BUG("unknown enum long_opt_lookup value '%d'", result);
>   }
>   >   static enum parse_opt_result parse_nodash_opt(struct parse_opt_ctx_t *p,
Rest of the patch looks good to me.
-- 
Sivaraam
Previous: Christian CouderNext: Christian Couder
Message 26 of 41 in “Standardize early option scanning to fix argument parsing bugs”
  1. 0/6 Standardize early option scanning to fix argument parsing bugsChristian Couder, Sep 2, 2026
  2. 1/6 parse-options: add early_scan_options()Christian Couder, Sep 2, 2026
  3. Junio C HamanoSep 2, 2026
  4. Christian CouderSep 23, 2026
  5. Junio C HamanoSep 23, 2026
  6. 2/6 bisect: fix "--" detection when a term name is "--"Christian Couder, Sep 2, 2026
  7. Junio C HamanoSep 2, 2026
  8. Christian CouderSep 23, 2026
  9. Junio C HamanoSep 23, 2026
  10. 3/6 rev-parse: fix "--" detection when it is an option valueChristian Couder, Sep 2, 2026
  11. 4/6 parse-options: add parse_options_takes_argument()Christian Couder, Sep 2, 2026
  12. 5/6 parse-options: build early scan options from a struct option arrayChristian Couder, Sep 2, 2026
  13. 6/6 fast-import: use early_scan_options() for --allow-unsafe-featuresChristian Couder, Sep 2, 2026
  14. Junio C HamanoSep 4, 2026
  15. Junio C HamanoSep 2, 2026
  16. Christian CouderSep 23, 2026
  17. 0/3 Standardize early option scanningChristian Couder, Sep 23, 2026
  18. 1/3 parse-options: add parse_options_takes_argument()Christian Couder, Sep 23, 2026
  19. 2/3 parse-options: add early_scan_options()Christian Couder, Sep 23, 2026
  20. Kaartic SivaraamSep 30, 2026
  21. Christian CouderOct 12, 2026
  22. 3/3 fast-import: use early_scan_options() for --allow-unsafe-featuresChristian Couder, Sep 23, 2026
  23. 0/4 Standardize early option scanningChristian Couder, Oct 12, 2026
  24. 1/4 parse-options: add parse_options_takes_argument()Christian Couder, Oct 12, 2026
  25. 2/4 parse-options: extract lookup_long_opt() from parse_long_opt()Christian Couder, Oct 12, 2026
  26. Kaartic SivaraamOct 30, 2026
  27. Christian CouderNov 2, 2026
  28. 3/4 parse-options: add early_scan_options()Christian Couder, Oct 12, 2026
  29. Kaartic SivaraamOct 30, 2026
  30. Christian CouderNov 2, 2026
  31. 4/4 fast-import: use early_scan_options() for --allow-unsafe-featuresChristian Couder, Oct 12, 2026
  32. Cynthia UcheOct 31, 2026
  33. Christian CouderNov 2, 2026
  34. Kaartic SivaraamOct 30, 2026
  35. 0/4 Standardize early option scanningChristian Couder, Nov 2, 2026
  36. 1/4 parse-options: add parse_options_takes_argument()Christian Couder, Nov 2, 2026
  37. 2/4 parse-options: extract lookup_long_opt() from parse_long_opt()Christian Couder, Nov 2, 2026
  38. 3/4 parse-options: add early_scan_options()Christian Couder, Nov 2, 2026
  39. 4/4 fast-import: use early_scan_options() for --allow-unsafe-featuresChristian Couder, Nov 2, 2026
  40. Kaartic SivaraamNov 3, 2026
  41. Junio C HamanoNov 3, 2026

Read the whole thread, see it on lore, or plain text.

$ cat FOOTERMessages come from the public archive at lore.kernel.org/git, fetched every hour. The front page is chosen and written each morning by an AI editor and can be wrong; the threads themselves are the record. About and API. For agents: an MCP server at https://gitlist.dev/mcp, and any thread, story or person page as Markdown by adding .md to its URL (or sending Accept: text/markdown). Details in /llms.txt.