Git development
 help / color / mirror / Atom feed
From: Christian Couder <christian.couder@gmail.com>
To: git@vger.kernel.org
Cc: Junio C Hamano <gitster@pobox.com>,
	Kaartic Sivaraam <kaartic.sivaraam@gmail.com>,
	Patrick Steinhardt <ps@pks.im>, Elijah Newren <newren@gmail.com>,
	Jeff King <peff@peff.net>,
	"brian m . carlson" <sandals@crustytoothpaste.net>,
	Johannes Schindelin <Johannes.Schindelin@gmx.de>,
	Justin Tobler <jltobler@gmail.com>,
	Christian Couder <christian.couder@gmail.com>
Subject: [PATCH v3 2/4] parse-options: extract lookup_long_opt() from parse_long_opt()
Date: Mon, 12 Oct 2026 09:44:11 +0200	[thread overview]
Message-ID: <20261012074413.337183-3-christian.couder@gmail.com> (raw)
In-Reply-To: <20261012074413.337183-1-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.

Signed-off-by: Christian Couder <christian.couder@gmail.com>
---
 parse-options.c | 105 +++++++++++++++++++++++++++++++++++++-----------
 1 file changed, 81 insertions(+), 24 deletions(-)

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);
+
+	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,
-- 
2.56.0.116.g6de20f6092


  parent reply	other threads:[~2026-10-12  7:44 UTC|newest]

Thread overview: 27+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-09-02 16:10 [PATCH 0/6] Standardize early option scanning to fix argument parsing bugs Christian Couder
2026-09-02 16:10 ` [PATCH 1/6] parse-options: add early_scan_options() Christian Couder
2026-09-02 22:11   ` Junio C Hamano
2026-09-23  8:10     ` Christian Couder
2026-09-23 17:25       ` Junio C Hamano
2026-09-02 16:10 ` [PATCH 2/6] bisect: fix "--" detection when a term name is "--" Christian Couder
2026-09-02 22:30   ` Junio C Hamano
2026-09-23  8:11     ` Christian Couder
2026-09-23 17:27       ` Junio C Hamano
2026-09-02 16:10 ` [PATCH 3/6] rev-parse: fix "--" detection when it is an option value Christian Couder
2026-09-02 16:10 ` [PATCH 4/6] parse-options: add parse_options_takes_argument() Christian Couder
2026-09-02 16:10 ` [PATCH 5/6] parse-options: build early scan options from a struct option array Christian Couder
2026-09-02 16:10 ` [PATCH 6/6] fast-import: use early_scan_options() for --allow-unsafe-features Christian Couder
2026-09-04  3:38   ` Junio C Hamano
2026-09-02 18:52 ` [PATCH 0/6] Standardize early option scanning to fix argument parsing bugs Junio C Hamano
2026-09-23  8:10   ` Christian Couder
2026-09-23  8:09 ` [PATCH v2 0/3] Standardize early option scanning Christian Couder
2026-09-23  8:09   ` [PATCH v2 1/3] parse-options: add parse_options_takes_argument() Christian Couder
2026-09-23  8:09   ` [PATCH v2 2/3] parse-options: add early_scan_options() Christian Couder
2026-09-30  7:13     ` Kaartic Sivaraam
2026-10-12  8:10       ` Christian Couder
2026-09-23  8:09   ` [PATCH v2 3/3] fast-import: use early_scan_options() for --allow-unsafe-features Christian Couder
2026-10-12  7:44   ` [PATCH v3 0/4] Standardize early option scanning Christian Couder
2026-10-12  7:44     ` [PATCH v3 1/4] parse-options: add parse_options_takes_argument() Christian Couder
2026-10-12  7:44     ` Christian Couder [this message]
2026-10-12  7:44     ` [PATCH v3 3/4] parse-options: add early_scan_options() Christian Couder
2026-10-12  7:44     ` [PATCH v3 4/4] fast-import: use early_scan_options() for --allow-unsafe-features Christian Couder

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20261012074413.337183-3-christian.couder@gmail.com \
    --to=christian.couder@gmail.com \
    --cc=Johannes.Schindelin@gmx.de \
    --cc=git@vger.kernel.org \
    --cc=gitster@pobox.com \
    --cc=jltobler@gmail.com \
    --cc=kaartic.sivaraam@gmail.com \
    --cc=newren@gmail.com \
    --cc=peff@peff.net \
    --cc=ps@pks.im \
    --cc=sandals@crustytoothpaste.net \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox