# [PATCH 0/3] nd/diff-parseopt fixups

10 messages from 2019-01-22 to 2019-01-22. Participants: Nguyễn Thái Ngọc Duy, Junio C Hamano.
Thread: https://gitlist.dev/t/50281

## Nguyễn Thái Ngọc Duy, 2019-01-22 00:26

Subject: [PATCH 0/3] nd/diff-parseopt fixups
Message-ID: <20190122002635.9411-1-pclouds@gmail.com>
URL: https://gitlist.dev/e/20190122002635.9411-1-pclouds%40gmail.com

```
Let me see if I could just fix up this series on top like this.
Failing this, I'll break it down and resend in small chunks.

This redoes a85e5a0108 (parse-options: allow keep-unknown +
stop-at-non-opt combination - 2019-01-17), deletes dead functions and
rewrites b9b760ed1c (diff.c: convert -B|--break-rewrites - 2019-01-17) a
bit.

Nguyễn Thái Ngọc Duy (3):
  Revert "parse-options: allow keep-unknown + stop-at-non-opt
    combination"
  diff.c: delete unused functions
  diff.c: simplify diff_opt_break_rewrites()

 diff.c          | 74 ++-----------------------------------------------
 parse-options.c |  4 +++
 2 files changed, 6 insertions(+), 72 deletions(-)

-- 
2.20.1.560.g70ca8b83ee


```

## Nguyễn Thái Ngọc Duy, 2019-01-22 00:26

Subject: [PATCH 1/3] Revert "parse-options: allow keep-unknown + stop-at-non-opt combination"
Message-ID: <20190122002635.9411-2-pclouds@gmail.com>
URL: https://gitlist.dev/e/20190122002635.9411-2-pclouds%40gmail.com
In-Reply-To: <20190122002635.9411-1-pclouds@gmail.com>

```
This reverts commit a85e5a01080e35da0f94bf98f5f334c64e214ce3, which is a
revert of 0d260f9a09 (parseopt: prevent KEEP_UNKNOWN and
STOP_AT_NON_OPTION from being used together - 2009-03-09).

As described in 0d260f9a09, this option combination is tricky to get
right and should be prevented unless you know what you're doing. In the
one-shot mode, the diff and revision parsers are very well aware of
remaining arguments being potentially unknown options or revisions and
should handle the combination safely.

Reinstate the check added by 0d260f9a09. Modify it a bit to let one-shot
mode through.

Noticed-by: Stefan Beller <sbeller@google.com>
---
 parse-options.c | 4 ++++
 1 file changed, 4 insertions(+)

diff --git a/parse-options.c b/parse-options.c
index a0ff8ea225..cec74522e5 100644
--- a/parse-options.c
+++ b/parse-options.c
@@ -466,6 +466,10 @@ void parse_options_start(struct parse_opt_ctx_t *ctx,
 	ctx->prefix = prefix;
 	ctx->cpidx = ((flags & PARSE_OPT_KEEP_ARGV0) != 0);
 	ctx->flags = flags;
+	if ((flags & PARSE_OPT_KEEP_UNKNOWN) &&
+	    (flags & PARSE_OPT_STOP_AT_NON_OPTION) &&
+	    !(flags & PARSE_OPT_ONE_SHOT))
+		BUG("STOP_AT_NON_OPTION and KEEP_UNKNOWN don't go together");
 	if ((flags & PARSE_OPT_ONE_SHOT) &&
 	    (flags & PARSE_OPT_KEEP_ARGV0))
 		BUG("Can't keep argv0 if you don't have it");
-- 
2.20.1.560.g70ca8b83ee


```

## Nguyễn Thái Ngọc Duy, 2019-01-22 00:26

Subject: [PATCH 3/3] diff.c: simplify diff_opt_break_rewrites()
Message-ID: <20190122002635.9411-4-pclouds@gmail.com>
URL: https://gitlist.dev/e/20190122002635.9411-4-pclouds%40gmail.com
In-Reply-To: <20190122002635.9411-1-pclouds@gmail.com>

```
Helped-by: Johannes Schindelin <Johannes.Schindelin@gmx.de>
---
 diff.c | 9 ++-------
 1 file changed, 2 insertions(+), 7 deletions(-)

diff --git a/diff.c b/diff.c
index da5ba835ce..2351571251 100644
--- a/diff.c
+++ b/diff.c
@@ -4814,20 +4814,15 @@ static int diff_opt_break_rewrites(const struct option *opt,
 				   const char *arg, int unset)
 {
 	int *break_opt = opt->value;
-	int opt1, opt2;
+	int opt1, opt2 = 0;
 
 	BUG_ON_OPT_NEG(unset);
 	if (!arg)
 		arg = "";
 	opt1 = parse_rename_score(&arg);
-	switch (*arg) {
-	case '\0':
-		opt2 = 0;
-		break;
-	case '/':
+	if (*arg == '/') {
 		arg++;
 		opt2 = parse_rename_score(&arg);
-		break;
 	}
 	if (*arg != 0)
 		return error(_("%s expects <n>/<m> form"), opt->long_name);
-- 
2.20.1.560.g70ca8b83ee


```

## Nguyễn Thái Ngọc Duy, 2019-01-22 00:26

Subject: [PATCH 2/3] diff.c: delete unused functions
Message-ID: <20190122002635.9411-3-pclouds@gmail.com>
URL: https://gitlist.dev/e/20190122002635.9411-3-pclouds%40gmail.com
In-Reply-To: <20190122002635.9411-1-pclouds@gmail.com>

```
After the parseopt conversion, a few functions are no longer
needed. Delete them.

Noticed-by: SZEDER Gábor <szeder.dev@gmail.com>
Noticed-by: Johannes Schindelin <Johannes.Schindelin@gmx.de>
---
 diff.c | 65 ----------------------------------------------------------
 1 file changed, 65 deletions(-)

diff --git a/diff.c b/diff.c
index 747e691190..da5ba835ce 100644
--- a/diff.c
+++ b/diff.c
@@ -104,11 +104,6 @@ static const char *color_diff_slots[] = {
 	[DIFF_FILE_NEW_BOLD]	      = "newBold",
 };
 
-static NORETURN void die_want_option(const char *option_name)
-{
-	die(_("option '%s' requires a value"), option_name);
-}
-
 define_list_config_array_extra(color_diff_slots, {"plain"});
 
 static int parse_diff_color_slot(const char *var)
@@ -4578,66 +4573,6 @@ void diff_setup_done(struct diff_options *options)
 	FREE_AND_NULL(options->parseopts);
 }
 
-static int opt_arg(const char *arg, int arg_short, const char *arg_long, int *val)
-{
-	char c, *eq;
-	int len;
-
-	if (*arg != '-')
-		return 0;
-	c = *++arg;
-	if (!c)
-		return 0;
-	if (c == arg_short) {
-		c = *++arg;
-		if (!c)
-			return 1;
-		if (val && isdigit(c)) {
-			char *end;
-			int n = strtoul(arg, &end, 10);
-			if (*end)
-				return 0;
-			*val = n;
-			return 1;
-		}
-		return 0;
-	}
-	if (c != '-')
-		return 0;
-	arg++;
-	eq = strchrnul(arg, '=');
-	len = eq - arg;
-	if (!len || strncmp(arg, arg_long, len))
-		return 0;
-	if (*eq) {
-		int n;
-		char *end;
-		if (!isdigit(*++eq))
-			return 0;
-		n = strtoul(eq, &end, 10);
-		if (*end)
-			return 0;
-		*val = n;
-	}
-	return 1;
-}
-
-static inline int short_opt(char opt, const char **argv,
-			    const char **optarg)
-{
-	const char *arg = argv[0];
-	if (arg[0] != '-' || arg[1] != opt)
-		return 0;
-	if (arg[2] != '\0') {
-		*optarg = arg + 2;
-		return 1;
-	}
-	if (!argv[1])
-		die("Option '%c' requires a value", opt);
-	*optarg = argv[1];
-	return 2;
-}
-
 int parse_long_opt(const char *opt, const char **argv,
 		   const char **optarg)
 {
-- 
2.20.1.560.g70ca8b83ee


```

## Nguyễn Thái Ngọc Duy, 2019-01-22 00:30

Subject: [PATCH v2 0/3] nd/diff-parseopt fixups
Message-ID: <20190122003020.9672-1-pclouds@gmail.com>
URL: https://gitlist.dev/e/20190122003020.9672-1-pclouds%40gmail.com
In-Reply-To: <20190122002635.9411-1-pclouds@gmail.com>

```
Resend with s-o-b. Oops.

Nguyễn Thái Ngọc Duy (3):
  Revert "parse-options: allow keep-unknown + stop-at-non-opt
    combination"
  diff.c: delete unused functions
  diff.c: simplify diff_opt_break_rewrites()

 diff.c          | 74 ++-----------------------------------------------
 parse-options.c |  4 +++
 2 files changed, 6 insertions(+), 72 deletions(-)

-- 
2.20.1.560.g70ca8b83ee


```

## Nguyễn Thái Ngọc Duy, 2019-01-22 00:30

Subject: [PATCH v2 1/3] Revert "parse-options: allow keep-unknown + stop-at-non-opt combination"
Message-ID: <20190122003020.9672-2-pclouds@gmail.com>
URL: https://gitlist.dev/e/20190122003020.9672-2-pclouds%40gmail.com
In-Reply-To: <20190122003020.9672-1-pclouds@gmail.com>

```
This reverts commit a85e5a01080e35da0f94bf98f5f334c64e214ce3, which is a
revert of 0d260f9a09 (parseopt: prevent KEEP_UNKNOWN and
STOP_AT_NON_OPTION from being used together - 2009-03-09).

As described in 0d260f9a09, this option combination is tricky to get
right and should be prevented unless you know what you're doing. In the
one-shot mode, the diff and revision parsers are very well aware of
remaining arguments being potentially unknown options or revisions and
should handle the combination safely.

Reinstate the check added by 0d260f9a09. Modify it a bit to let one-shot
mode through.

Noticed-by: Stefan Beller <sbeller@google.com>
Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>
---
 parse-options.c | 4 ++++
 1 file changed, 4 insertions(+)

diff --git a/parse-options.c b/parse-options.c
index a0ff8ea225..cec74522e5 100644
--- a/parse-options.c
+++ b/parse-options.c
@@ -466,6 +466,10 @@ void parse_options_start(struct parse_opt_ctx_t *ctx,
 	ctx->prefix = prefix;
 	ctx->cpidx = ((flags & PARSE_OPT_KEEP_ARGV0) != 0);
 	ctx->flags = flags;
+	if ((flags & PARSE_OPT_KEEP_UNKNOWN) &&
+	    (flags & PARSE_OPT_STOP_AT_NON_OPTION) &&
+	    !(flags & PARSE_OPT_ONE_SHOT))
+		BUG("STOP_AT_NON_OPTION and KEEP_UNKNOWN don't go together");
 	if ((flags & PARSE_OPT_ONE_SHOT) &&
 	    (flags & PARSE_OPT_KEEP_ARGV0))
 		BUG("Can't keep argv0 if you don't have it");
-- 
2.20.1.560.g70ca8b83ee


```

## Nguyễn Thái Ngọc Duy, 2019-01-22 00:30

Subject: [PATCH v2 2/3] diff.c: delete unused functions
Message-ID: <20190122003020.9672-3-pclouds@gmail.com>
URL: https://gitlist.dev/e/20190122003020.9672-3-pclouds%40gmail.com
In-Reply-To: <20190122003020.9672-1-pclouds@gmail.com>

```
After the parseopt conversion, a few functions are no longer
needed. Delete them.

Noticed-by: SZEDER Gábor <szeder.dev@gmail.com>
Noticed-by: Johannes Schindelin <Johannes.Schindelin@gmx.de>
Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>
---
 diff.c | 65 ----------------------------------------------------------
 1 file changed, 65 deletions(-)

diff --git a/diff.c b/diff.c
index 747e691190..da5ba835ce 100644
--- a/diff.c
+++ b/diff.c
@@ -104,11 +104,6 @@ static const char *color_diff_slots[] = {
 	[DIFF_FILE_NEW_BOLD]	      = "newBold",
 };
 
-static NORETURN void die_want_option(const char *option_name)
-{
-	die(_("option '%s' requires a value"), option_name);
-}
-
 define_list_config_array_extra(color_diff_slots, {"plain"});
 
 static int parse_diff_color_slot(const char *var)
@@ -4578,66 +4573,6 @@ void diff_setup_done(struct diff_options *options)
 	FREE_AND_NULL(options->parseopts);
 }
 
-static int opt_arg(const char *arg, int arg_short, const char *arg_long, int *val)
-{
-	char c, *eq;
-	int len;
-
-	if (*arg != '-')
-		return 0;
-	c = *++arg;
-	if (!c)
-		return 0;
-	if (c == arg_short) {
-		c = *++arg;
-		if (!c)
-			return 1;
-		if (val && isdigit(c)) {
-			char *end;
-			int n = strtoul(arg, &end, 10);
-			if (*end)
-				return 0;
-			*val = n;
-			return 1;
-		}
-		return 0;
-	}
-	if (c != '-')
-		return 0;
-	arg++;
-	eq = strchrnul(arg, '=');
-	len = eq - arg;
-	if (!len || strncmp(arg, arg_long, len))
-		return 0;
-	if (*eq) {
-		int n;
-		char *end;
-		if (!isdigit(*++eq))
-			return 0;
-		n = strtoul(eq, &end, 10);
-		if (*end)
-			return 0;
-		*val = n;
-	}
-	return 1;
-}
-
-static inline int short_opt(char opt, const char **argv,
-			    const char **optarg)
-{
-	const char *arg = argv[0];
-	if (arg[0] != '-' || arg[1] != opt)
-		return 0;
-	if (arg[2] != '\0') {
-		*optarg = arg + 2;
-		return 1;
-	}
-	if (!argv[1])
-		die("Option '%c' requires a value", opt);
-	*optarg = argv[1];
-	return 2;
-}
-
 int parse_long_opt(const char *opt, const char **argv,
 		   const char **optarg)
 {
-- 
2.20.1.560.g70ca8b83ee


```

## Nguyễn Thái Ngọc Duy, 2019-01-22 00:30

Subject: [PATCH v2 3/3] diff.c: simplify diff_opt_break_rewrites()
Message-ID: <20190122003020.9672-4-pclouds@gmail.com>
URL: https://gitlist.dev/e/20190122003020.9672-4-pclouds%40gmail.com
In-Reply-To: <20190122003020.9672-1-pclouds@gmail.com>

```
Helped-by: Johannes Schindelin <Johannes.Schindelin@gmx.de>
Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>
---
 diff.c | 9 ++-------
 1 file changed, 2 insertions(+), 7 deletions(-)

diff --git a/diff.c b/diff.c
index da5ba835ce..2351571251 100644
--- a/diff.c
+++ b/diff.c
@@ -4814,20 +4814,15 @@ static int diff_opt_break_rewrites(const struct option *opt,
 				   const char *arg, int unset)
 {
 	int *break_opt = opt->value;
-	int opt1, opt2;
+	int opt1, opt2 = 0;
 
 	BUG_ON_OPT_NEG(unset);
 	if (!arg)
 		arg = "";
 	opt1 = parse_rename_score(&arg);
-	switch (*arg) {
-	case '\0':
-		opt2 = 0;
-		break;
-	case '/':
+	if (*arg == '/') {
 		arg++;
 		opt2 = parse_rename_score(&arg);
-		break;
 	}
 	if (*arg != 0)
 		return error(_("%s expects <n>/<m> form"), opt->long_name);
-- 
2.20.1.560.g70ca8b83ee


```

## Junio C Hamano, 2019-01-22 20:50

Subject: Re: [PATCH 3/3] diff.c: simplify diff_opt_break_rewrites()
Message-ID: <xmqqtvi0e7sv.fsf@gitster-ct.c.googlers.com>
URL: https://gitlist.dev/e/xmqqtvi0e7sv.fsf%40gitster-ct.c.googlers.com
In-Reply-To: <20190122002635.9411-4-pclouds@gmail.com>

```
Nguyễn Thái Ngọc Duy  <pclouds@gmail.com> writes:

> -	int opt1, opt2;
> +	int opt1, opt2 = 0;
>  
>  	BUG_ON_OPT_NEG(unset);
>  	if (!arg)
>  		arg = "";
>  	opt1 = parse_rename_score(&arg);
> -	switch (*arg) {
> -	case '\0':
> -		opt2 = 0;
> -		break;
> -	case '/':
> +	if (*arg == '/') {
>  		arg++;
>  		opt2 = parse_rename_score(&arg);
> -		break;
>  	}
>  	if (*arg != 0)
>  		return error(_("%s expects <n>/<m> form"), opt->long_name);

Good.  I was about to complain on the lack of "default" that catches
the error at the end of "<n>" in the switch(), but because this
follow-up validation is trying to catch "<n>" form (i.e. single
score without slash) whose "<n>" is malformed, and "<n>/<m>" form
whose "<m>" is malformed, which is kind of clever, but it is not
very easy to understand what is going on, it makes sense to get rid
of the switch() and make it if() statement.

It would make it even easier to follow if you did

	if (*arg == '/') {
		opt2 = ...;
		arg++;
	} else {
		opt2 = 0;
	}
	if (*arg)
		return error(...);

It makes it clear that opt2==0 means <n> form and not <n>/<m> form,
by having an explicit assignment while we parse what *arg points at,
without the initialization to 0 in the variable definition.

But this should be squashed in the original patch.  Having an "oops,
it was horribly unreadble---how about doing something like this?"
incremental is good during a discussion, and having a "what we have
seen is basically good and proven solid, but here is a small
improvement" incremental is good for code that is stable enough to
build on (read: at least in 'next'), but it is not particularly good
for a topic not yet in 'next' yet.



```

## Junio C Hamano, 2019-01-22 23:18

Subject: Re: [PATCH 3/3] diff.c: simplify diff_opt_break_rewrites()
Message-ID: <xmqqpnsocmdh.fsf@gitster-ct.c.googlers.com>
URL: https://gitlist.dev/e/xmqqpnsocmdh.fsf%40gitster-ct.c.googlers.com
In-Reply-To: <xmqqtvi0e7sv.fsf@gitster-ct.c.googlers.com>

```
Junio C Hamano <gitster@pobox.com> writes:

> It would make it even easier to follow if you did
>
> 	if (*arg == '/') {
> 		opt2 = ...;
> 		arg++;
> 	} else {

Oops, this should read "else if (!*arg) {", of course, to match the
original.  Sorry for the noise.

> 		opt2 = 0;
> 	}

And then we'd want a blank line here to make it clear that the
previous if/else cascade has finished, and the error checking we see
next is not part of it.

> 	if (*arg)
> 		return error(...);
>
> It makes it clear that opt2==0 means <n> form and not <n>/<m> form,
> by having an explicit assignment while we parse what *arg points at,
> without the initialization to 0 in the variable definition.


```
