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

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

From
Christian Couder <christian.couder@gmail.com>
Date
Nov 2, 2026, 15:14 UTC
Message-ID
<20261102151447.734680-8-christian.couder@gmail.com>
In-Reply-To
<20261102151447.734680-6-christian.couder@gmail.com>

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.

Helped-by: Kaartic Sivaraam <kaartic.sivaraam@gmail.com>
Signed-off-by: Christian Couder <christian.couder@gmail.com>
---
 parse-options.c | 117 ++++++++++++++++++++++++++++++++++++------------
 1 file changed, 89 insertions(+), 28 deletions(-)
diff --git a/parse-options.c b/parse-options.c
index d7cbb5a343..d94d6fed93 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,74 @@ 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))
-		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);
-		return PARSE_OPT_HELP_ERROR;
-	}
-	if (abbrev.option) {
+	if (other->option)
+		return LONG_OPT_AMBIGUOUS;
+
+	if (found->option) {
 		if (*arg_end)
-			p->opt = arg_end + 1;
-		return get_value(p, abbrev.option, abbrev.flags);
+			*stuck = arg_end + 1;
+		return LONG_OPT_ABBREV;
 	}
-	return PARSE_OPT_UNKNOWN;
+
+	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);
+
+	switch (result) {
+	case LONG_OPT_UNKNOWN:
+		return PARSE_OPT_UNKNOWN;
+	case LONG_OPT_ABBREV:
+	case LONG_OPT_AMBIGUOUS:
+		if (disallow_abbreviated_options) {
+			const char *arg_end = strchrnul(arg, '=');
+			die("disallowed abbreviated or ambiguous option '%.*s'",
+			    (int)(arg_end - arg), arg);
+		}
+		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;
+		}
+		/* fallthru */
+	case LONG_OPT_EXACT:
+		p->opt = stuck;
+		return get_value(p, found.option, found.flags);
+	}
+
+	BUG("unknown enum long_opt_lookup value '%d'", result);
 }
 
 static enum parse_opt_result parse_nodash_opt(struct parse_opt_ctx_t *p,
-- 
2.56.0.4.gceeb21e89e
Previous: Christian CouderNext: Christian Couder
Message 37 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.