All of lore.kernel.org
 help / color / mirror / Atom feed
From: Junio C Hamano <gitster@pobox.com>
To: "René Scharfe" <l.s.r@web.de>
Cc: Jeff King <peff@peff.net>,  git@vger.kernel.org
Subject: Re: [Bug?] "git diff --no-rename A B"
Date: Sat, 20 Jan 2024 09:55:17 -0800	[thread overview]
Message-ID: <xmqqmsszga9m.fsf@gitster.g> (raw)
In-Reply-To: <579fd5bc-3bfd-427f-b22d-dab5e7e56eb2@web.de> ("René Scharfe"'s message of "Sat, 20 Jan 2024 15:39:38 +0100")

René Scharfe <l.s.r@web.de> writes:

> Because diff_opt_parse() passes PARSE_OPT_KEEP_UNKNOWN_OPT, which makes
> parse_long_opt() skip abbreviation detection.  Which it does since
> baa4adc66a (parse-options: disable option abbreviation with
> PARSE_OPT_KEEP_UNKNOWN, 2019-01-27).
>
> It added a condition only to the if.  Its body can be reached via goto
> from two other places, though.  So abbreviated options are effectively
> allowed through the back door.

Wow, that is nasty.  Thanks for spotting.

I agree with you that the structure of that loop and the processing
in it does look confusing.

> --- >8 ---
> Subject: [PATCH] parse-options: fully disable option abbreviation with PARSE_OPT_KEEP_UNKNOWN
>
> baa4adc66a (parse-options: disable option abbreviation with
> PARSE_OPT_KEEP_UNKNOWN, 2019-01-27) turned off support for abbreviated
> options when the flag PARSE_OPT_KEEP_UNKNOWN is given, as any shortened
> option could also be an abbreviation for one of the unknown options.
>
> The code for handling abbreviated options is guarded by an if, but it
> can also be reached via goto.  baa4adc66a only blocked the first way.
> Add the condition to the other ones as well.
>
> Signed-off-by: René Scharfe <l.s.r@web.de>
> ---
> Formatted with --function-context for easier review.
> The code is a quite tangled, any ideas how to structure it better?
>
>  parse-options.c         | 8 +++++---
>  t/t4013-diff-various.sh | 6 ++++++
>  2 files changed, 11 insertions(+), 3 deletions(-)
>
> diff --git a/parse-options.c b/parse-options.c
> index 4ce2b7ca16..92e96ca6cd 100644
> --- a/parse-options.c
> +++ b/parse-options.c
> @@ -353,93 +353,95 @@ static int is_alias(struct parse_opt_ctx_t *ctx,
>  static enum parse_opt_result parse_long_opt(
>  	struct parse_opt_ctx_t *p, const char *arg,
>  	const struct option *options)
>  {
>  	const char *arg_end = strchrnul(arg, '=');
>  	const struct option *abbrev_option = NULL, *ambiguous_option = NULL;
>  	enum opt_parsed abbrev_flags = OPT_LONG, ambiguous_flags = OPT_LONG;
> +	int allow_abbrev = !(p->flags & PARSE_OPT_KEEP_UNKNOWN_OPT);
>
>  	for (; options->type != OPTION_END; options++) {
>  		const char *rest, *long_name = options->long_name;
>  		enum opt_parsed flags = OPT_LONG, opt_flags = OPT_LONG;
>
>  		if (options->type == OPTION_SUBCOMMAND)
>  			continue;
>  		if (!long_name)
>  			continue;
>
>  again:
>  		if (!skip_prefix(arg, long_name, &rest))
>  			rest = NULL;
>  		if (!rest) {
>  			/* abbreviated? */
> -			if (!(p->flags & PARSE_OPT_KEEP_UNKNOWN_OPT) &&
> +			if (allow_abbrev &&
>  			    !strncmp(long_name, arg, arg_end - arg)) {
>  is_abbreviated:
>  				if (abbrev_option &&
>  				    !is_alias(p, abbrev_option, options)) {
>  					/*
>  					 * If this is abbreviated, it is
>  					 * ambiguous. So when there is no
>  					 * exact match later, we need to
>  					 * error out.
>  					 */
>  					ambiguous_option = abbrev_option;
>  					ambiguous_flags = abbrev_flags;
>  				}
>  				if (!(flags & OPT_UNSET) && *arg_end)
>  					p->opt = arg_end + 1;
>  				abbrev_option = options;
>  				abbrev_flags = flags ^ opt_flags;
>  				continue;
>  			}
>  			/* negation allowed? */
>  			if (options->flags & PARSE_OPT_NONEG)
>  				continue;
>  			/* negated and abbreviated very much? */
> -			if (starts_with("no-", arg)) {
> +			if (allow_abbrev && starts_with("no-", arg)) {
>  				flags |= OPT_UNSET;
>  				goto is_abbreviated;
>  			}
>  			/* negated? */
>  			if (!starts_with(arg, "no-")) {
>  				if (skip_prefix(long_name, "no-", &long_name)) {
>  					opt_flags |= OPT_UNSET;
>  					goto again;
>  				}
>  				continue;
>  			}
>  			flags |= OPT_UNSET;
>  			if (!skip_prefix(arg + 3, long_name, &rest)) {
>  				/* abbreviated and negated? */
> -				if (starts_with(long_name, arg + 3))
> +				if (allow_abbrev &&
> +				    starts_with(long_name, arg + 3))
>  					goto is_abbreviated;
>  				else
>  					continue;
>  			}
>  		}
>  		if (*rest) {
>  			if (*rest != '=')
>  				continue;
>  			p->opt = rest + 1;
>  		}
>  		return get_value(p, options, flags ^ opt_flags);
>  	}
>
>  	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;
>  	}
>  	if (abbrev_option)
>  		return get_value(p, abbrev_option, abbrev_flags);
>  	return PARSE_OPT_UNKNOWN;
>  }
> diff --git a/t/t4013-diff-various.sh b/t/t4013-diff-various.sh
> index cb094241ec..1e3b2dbea4 100755
> --- a/t/t4013-diff-various.sh
> +++ b/t/t4013-diff-various.sh
> @@ -663,4 +663,10 @@ test_expect_success 'diff --default-prefix overrides diff.mnemonicprefix' '
>  	check_prefix actual a/file0 b/file0
>  '
>
> +test_expect_success 'diff --no-renames cannot be abbreviated' '
> +	test_expect_code 129 git diff --no-rename >actual 2>error &&
> +	test_must_be_empty actual &&
> +	grep "invalid option: --no-rename" error
> +'
> +
>  test_done
> --
> 2.43.0

  reply	other threads:[~2024-01-20 17:55 UTC|newest]

Thread overview: 8+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2024-01-18  1:07 [Bug?] "git diff --no-rename A B" Junio C Hamano
2024-01-18  6:26 ` Dragan Simic
2024-01-20  1:18 ` Jeff King
2024-01-20 14:39   ` René Scharfe
2024-01-20 17:55     ` Junio C Hamano [this message]
2024-01-21 17:56       ` René Scharfe
2024-01-22 23:00     ` Jeff King
2024-01-23  0:28       ` Jeff King

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=xmqqmsszga9m.fsf@gitster.g \
    --to=gitster@pobox.com \
    --cc=git@vger.kernel.org \
    --cc=l.s.r@web.de \
    --cc=peff@peff.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 an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.