diff mbox series

[v4,07/10] cat-file: fix remaining usage bugs

Message ID patch-v4-07.10-878d9052bfb-20211208T123151Z-avarab@gmail.com (mailing list archive)
State Superseded
Headers show
Series cat-file: better usage UX & error messages | expand

Commit Message

Ævar Arnfjörð Bjarmason Dec. 8, 2021, 12:34 p.m. UTC
With the migration of --batch-all-objects to OPT_CMDMODE() in the
preceding commit one bug with combining it and other OPT_CMDMODE()
options was solved, but we were still left with e.g. --buffer silently
being discarded when not in batch mode.

Fix all those bugs, and in addition emit errors telling the user
specifically what options can't be combined with what other options,
before this we'd usually just emit the cryptic usage text and leave
the users to work it out by themselves.

This change is rather large, because to do so we need to untangle the
options processing so that we can not only error out, but emit
sensible errors, and e.g. emit errors about options before errors
about stray argc elements (as they might become valid if the option
were removed).

Some of the output changes ("error:" to "fatal:" with
usage_msg_opt[f]()), but none of the exit codes change, except in
those cases where we silently accepted bad option combinations before,
now we'll error out.

Signed-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>
---
 builtin/cat-file.c  | 95 ++++++++++++++++++++++++++++++---------------
 t/t1006-cat-file.sh | 41 +++++++++----------
 2 files changed, 84 insertions(+), 52 deletions(-)

Comments

John Cai Dec. 20, 2021, 4 p.m. UTC | #1
> On Dec 8, 2021, at 7:34 AM, Ævar Arnfjörð Bjarmason <avarab@gmail.com> wrote:
> 
> With the migration of --batch-all-objects to OPT_CMDMODE() in the
> preceding commit one bug with combining it and other OPT_CMDMODE()
> options was solved, but we were still left with e.g. --buffer silently
> being discarded when not in batch mode.
> 
> Fix all those bugs, and in addition emit errors telling the user
> specifically what options can't be combined with what other options,
> before this we'd usually just emit the cryptic usage text and leave
> the users to work it out by themselves.
> 
> This change is rather large, because to do so we need to untangle the
> options processing so that we can not only error out, but emit
> sensible errors, and e.g. emit errors about options before errors
> about stray argc elements (as they might become valid if the option
> were removed).
> 
> Some of the output changes ("error:" to "fatal:" with
> usage_msg_opt[f]()), but none of the exit codes change, except in
> those cases where we silently accepted bad option combinations before,
> now we'll error out.
> 
> Signed-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>
> ---
> builtin/cat-file.c  | 95 ++++++++++++++++++++++++++++++---------------
> t/t1006-cat-file.sh | 41 +++++++++----------
> 2 files changed, 84 insertions(+), 52 deletions(-)
> 
> diff --git a/builtin/cat-file.c b/builtin/cat-file.c
> index 87356208134..1087f0f4a85 100644
> --- a/builtin/cat-file.c
> +++ b/builtin/cat-file.c
> @@ -648,6 +648,8 @@ static int batch_option_callback(const struct option *opt,
> int cmd_cat_file(int argc, const char **argv, const char *prefix)
> {
> 	int opt = 0;
> +	int opt_cw = 0;
> +	int opt_epts = 0;
> 	const char *exp_type = NULL, *obj_name = NULL;
> 	struct batch_options batch = {0};
> 	int unknown_type = 0;
> @@ -701,45 +703,74 @@ int cmd_cat_file(int argc, const char **argv, const char *prefix)
> 	batch.buffer_output = -1;
> 
> 	argc = parse_options(argc, argv, prefix, options, usage, 0);
> -	if (argc && batch.enabled)
> -		usage_with_options(usage, options);
> -	if (opt == 'b') {
> -		batch.all_objects = 1;
> -	} else if (opt) {
> -		if (batch.enabled && (opt == 'c' || opt == 'w'))
> -			batch.cmdmode = opt;
> -		else if (argc == 1)
> -			obj_name = argv[0];
> -		else
> -			usage_with_options(usage, options);
> -	} else if (!opt && !batch.enabled) {
> -		if (argc == 2) {
> -			exp_type = argv[0];
> -			obj_name = argv[1];
> -		} else
> -			usage_with_options(usage, options);
> -	} else if (batch.enabled && batch.cmdmode != opt)
> -		usage_with_options(usage, options);
> +	opt_cw = (opt == 'c' || opt == 'w');
> +	opt_epts = (opt == 'e' || opt == 'p' || opt == 't' || opt == 's');
> 
> -	if ((batch.follow_symlinks || batch.all_objects) && !batch.enabled) {
> -		usage_with_options(usage, options);
> -	}
> -
> -	if (force_path && opt != 'c' && opt != 'w') {
> -		error("--path=<path> needs --textconv or --filters");
> -		usage_with_options(usage, options);
> -	}
> +	/* --batch-all-objects? */
> +	if (opt == 'b')
> +		batch.all_objects = 1;
> 
> -	if (force_path && batch.enabled) {
> -		error("--path=<path> incompatible with --batch");
> -		usage_with_options(usage, options);
> -	}
> +	/* Option compatibility */
> +	if (force_path && !opt_cw)
> +		usage_msg_optf(_("'%s=<%s>' needs '%s' or '%s'"),
> +			       usage, options,
> +			       "--path", _("path|tree-ish"), "--filters",
> +			       "--textconv");
> 
> +	/* Option compatibility with batch mode */
> +	if (batch.enabled)
> +		;
> +	else if (batch.follow_symlinks)
> +		usage_msg_optf(_("'%s' requires a batch mode"), usage, options,
> +			       "--follow_symlinks");
> +	else if (batch.buffer_output >= 0)
> +		usage_msg_optf(_("'%s' requires a batch mode"), usage, options,
> +			       "--buffer");
> +	else if (batch.all_objects)
> +		usage_msg_optf(_("'%s' requires a batch mode"), usage, options,
> +			       "--batch-all_objects");

s/_/- for the last argument in the above usage_msg_optf calls

> +
> +	/* Batch defaults */
> 	if (batch.buffer_output < 0)
> 		batch.buffer_output = batch.all_objects;
> 
> -	if (batch.enabled)
> +	/* Return early if we're in batch mode? */
> +	if (batch.enabled) {
> +		if (opt_cw)
> +			batch.cmdmode = opt;
> +		else if (opt && opt != 'b')
> +			usage_msg_optf(_("'-%c' is incompatible with batch mode"),
> +				       usage, options, opt);
> +		else if (argc)
> +			usage_msg_opt(_("batch modes take no arguments"), usage,
> +				      options);
> +
> 		return batch_objects(&batch);
> +	}
> +
> +	if (opt) {
> +		if (!argc && opt == 'c')
> +			usage_msg_optf(_("<rev> required with '%s'"),
> +				       usage, options, "--textconv");
> +		else if (!argc && opt == 'w')
> +			usage_msg_optf(_("<rev> required with '%s'"),
> +				       usage, options, "--filters");
> +		else if (!argc && opt_epts)
> +			usage_msg_optf(_("<object> required with '-%c'"),
> +				       usage, options, opt);
> +		else if (argc == 1)
> +			obj_name = argv[0];
> +		else
> +			usage_msg_opt(_("too many arguments"), usage, options);
> +	} else if (!argc) {
> +		usage_with_options(usage, options);
> +	} else if (argc != 2) {
> +		usage_msg_optf(_("only two arguments allowed in <type> <object> mode, not %d"),
> +			      usage, options, argc);
> +	} else if (argc) {
> +		exp_type = argv[0];
> +		obj_name = argv[1];
> +	}
> 
> 	if (unknown_type && opt != 't' && opt != 's')
> 		die("git cat-file --allow-unknown-type: use with -s or -t");
> diff --git a/t/t1006-cat-file.sh b/t/t1006-cat-file.sh
> index ebec2061d25..123801cfe2a 100755
> --- a/t/t1006-cat-file.sh
> +++ b/t/t1006-cat-file.sh
> @@ -24,7 +24,7 @@ done
> 
> test_incompatible_usage () {
> 	test_expect_code 129 "$@" 2>err &&
> -	grep -E "^error:.**needs" err
> +	grep -E "^(fatal|error):.*(requires|incompatible with|needs)" err
> }
> 
> for opt in --batch --batch-check
> @@ -34,48 +34,54 @@ do
> 	'
> done
> 
> +test_missing_usage() {
> +	test_expect_code 129 "$@" 2>err &&
> +	grep -E "^fatal:.*required" err
> +}
> +
> short_modes="-e -p -t -s"
> cw_modes="--textconv --filters"
> 
> for opt in $cw_modes
> do
> 	test_expect_success "usage: $opt requires another option" '
> -		test_expect_code 129 git cat-file $opt
> +		test_missing_usage git cat-file $opt
> 	'
> done
> 
> for opt in $short_modes
> do
> 	test_expect_success "usage: $opt requires another option" '
> -		test_expect_code 129 git cat-file $opt
> +		test_missing_usage git cat-file $opt
> 	'
> 
> 	for opt2 in --batch \
> 		--batch-check \
> -		--follow-symlinks
> +		--follow-symlinks \
> +		"--path=foo HEAD:some-path.txt"
> 	do
> -		test_expect_failure "usage: incompatible options: $opt and $opt2" '
> +		test_expect_success "usage: incompatible options: $opt and $opt2" '
> 			test_incompatible_usage git cat-file $opt $opt2
> 		'
> 	done
> -
> -	opt2="--path=foo HEAD:some-path.txt"
> -	test_expect_success "usage: incompatible options: $opt and $opt2" '
> -		test_incompatible_usage git cat-file $opt $opt2
> -	'
> done
> 
> +test_too_many_arguments() {
> +	test_expect_code 129 "$@" 2>err &&
> +	grep -E "^fatal: too many arguments$" err
> +}
> +
> for opt in $short_modes $cw_modes
> do
> 	args="one two three"
> 	test_expect_success "usage: too many arguments: $opt $args" '
> -		test_expect_code 129 git cat-file $opt $args
> +		test_too_many_arguments git cat-file $opt $args
> 	'
> 
> 	for opt2 in --buffer --follow-symlinks
> 	do
> 		test_expect_success "usage: incompatible arguments: $opt with batch option $opt2" '
> -			test_expect_code 129 git cat-file $opt $opt2
> +			test_incompatible_usage git cat-file $opt $opt2
> 		'
> 	done
> done
> @@ -84,14 +90,9 @@ for opt in --buffer \
> 	--follow-symlinks \
> 	--batch-all-objects
> do
> -	status=success
> -	if test $opt = "--buffer"
> -	then
> -		status=failure
> -	fi
> -	test_expect_$status "usage: bad option combination: $opt without batch mode" '
> -		test_expect_code 129 git cat-file $opt &&
> -		test_expect_code 129 git cat-file $opt commit HEAD
> +	test_expect_success "usage: bad option combination: $opt without batch mode" '
> +		test_incompatible_usage git cat-file $opt &&
> +		test_incompatible_usage git cat-file $opt commit HEAD
> 	'
> done
> 
> -- 
> 2.34.1.926.g895e15e0c0c
>
diff mbox series

Patch

diff --git a/builtin/cat-file.c b/builtin/cat-file.c
index 87356208134..1087f0f4a85 100644
--- a/builtin/cat-file.c
+++ b/builtin/cat-file.c
@@ -648,6 +648,8 @@  static int batch_option_callback(const struct option *opt,
 int cmd_cat_file(int argc, const char **argv, const char *prefix)
 {
 	int opt = 0;
+	int opt_cw = 0;
+	int opt_epts = 0;
 	const char *exp_type = NULL, *obj_name = NULL;
 	struct batch_options batch = {0};
 	int unknown_type = 0;
@@ -701,45 +703,74 @@  int cmd_cat_file(int argc, const char **argv, const char *prefix)
 	batch.buffer_output = -1;
 
 	argc = parse_options(argc, argv, prefix, options, usage, 0);
-	if (argc && batch.enabled)
-		usage_with_options(usage, options);
-	if (opt == 'b') {
-		batch.all_objects = 1;
-	} else if (opt) {
-		if (batch.enabled && (opt == 'c' || opt == 'w'))
-			batch.cmdmode = opt;
-		else if (argc == 1)
-			obj_name = argv[0];
-		else
-			usage_with_options(usage, options);
-	} else if (!opt && !batch.enabled) {
-		if (argc == 2) {
-			exp_type = argv[0];
-			obj_name = argv[1];
-		} else
-			usage_with_options(usage, options);
-	} else if (batch.enabled && batch.cmdmode != opt)
-		usage_with_options(usage, options);
+	opt_cw = (opt == 'c' || opt == 'w');
+	opt_epts = (opt == 'e' || opt == 'p' || opt == 't' || opt == 's');
 
-	if ((batch.follow_symlinks || batch.all_objects) && !batch.enabled) {
-		usage_with_options(usage, options);
-	}
-
-	if (force_path && opt != 'c' && opt != 'w') {
-		error("--path=<path> needs --textconv or --filters");
-		usage_with_options(usage, options);
-	}
+	/* --batch-all-objects? */
+	if (opt == 'b')
+		batch.all_objects = 1;
 
-	if (force_path && batch.enabled) {
-		error("--path=<path> incompatible with --batch");
-		usage_with_options(usage, options);
-	}
+	/* Option compatibility */
+	if (force_path && !opt_cw)
+		usage_msg_optf(_("'%s=<%s>' needs '%s' or '%s'"),
+			       usage, options,
+			       "--path", _("path|tree-ish"), "--filters",
+			       "--textconv");
 
+	/* Option compatibility with batch mode */
+	if (batch.enabled)
+		;
+	else if (batch.follow_symlinks)
+		usage_msg_optf(_("'%s' requires a batch mode"), usage, options,
+			       "--follow_symlinks");
+	else if (batch.buffer_output >= 0)
+		usage_msg_optf(_("'%s' requires a batch mode"), usage, options,
+			       "--buffer");
+	else if (batch.all_objects)
+		usage_msg_optf(_("'%s' requires a batch mode"), usage, options,
+			       "--batch-all_objects");
+
+	/* Batch defaults */
 	if (batch.buffer_output < 0)
 		batch.buffer_output = batch.all_objects;
 
-	if (batch.enabled)
+	/* Return early if we're in batch mode? */
+	if (batch.enabled) {
+		if (opt_cw)
+			batch.cmdmode = opt;
+		else if (opt && opt != 'b')
+			usage_msg_optf(_("'-%c' is incompatible with batch mode"),
+				       usage, options, opt);
+		else if (argc)
+			usage_msg_opt(_("batch modes take no arguments"), usage,
+				      options);
+
 		return batch_objects(&batch);
+	}
+
+	if (opt) {
+		if (!argc && opt == 'c')
+			usage_msg_optf(_("<rev> required with '%s'"),
+				       usage, options, "--textconv");
+		else if (!argc && opt == 'w')
+			usage_msg_optf(_("<rev> required with '%s'"),
+				       usage, options, "--filters");
+		else if (!argc && opt_epts)
+			usage_msg_optf(_("<object> required with '-%c'"),
+				       usage, options, opt);
+		else if (argc == 1)
+			obj_name = argv[0];
+		else
+			usage_msg_opt(_("too many arguments"), usage, options);
+	} else if (!argc) {
+		usage_with_options(usage, options);
+	} else if (argc != 2) {
+		usage_msg_optf(_("only two arguments allowed in <type> <object> mode, not %d"),
+			      usage, options, argc);
+	} else if (argc) {
+		exp_type = argv[0];
+		obj_name = argv[1];
+	}
 
 	if (unknown_type && opt != 't' && opt != 's')
 		die("git cat-file --allow-unknown-type: use with -s or -t");
diff --git a/t/t1006-cat-file.sh b/t/t1006-cat-file.sh
index ebec2061d25..123801cfe2a 100755
--- a/t/t1006-cat-file.sh
+++ b/t/t1006-cat-file.sh
@@ -24,7 +24,7 @@  done
 
 test_incompatible_usage () {
 	test_expect_code 129 "$@" 2>err &&
-	grep -E "^error:.**needs" err
+	grep -E "^(fatal|error):.*(requires|incompatible with|needs)" err
 }
 
 for opt in --batch --batch-check
@@ -34,48 +34,54 @@  do
 	'
 done
 
+test_missing_usage() {
+	test_expect_code 129 "$@" 2>err &&
+	grep -E "^fatal:.*required" err
+}
+
 short_modes="-e -p -t -s"
 cw_modes="--textconv --filters"
 
 for opt in $cw_modes
 do
 	test_expect_success "usage: $opt requires another option" '
-		test_expect_code 129 git cat-file $opt
+		test_missing_usage git cat-file $opt
 	'
 done
 
 for opt in $short_modes
 do
 	test_expect_success "usage: $opt requires another option" '
-		test_expect_code 129 git cat-file $opt
+		test_missing_usage git cat-file $opt
 	'
 
 	for opt2 in --batch \
 		--batch-check \
-		--follow-symlinks
+		--follow-symlinks \
+		"--path=foo HEAD:some-path.txt"
 	do
-		test_expect_failure "usage: incompatible options: $opt and $opt2" '
+		test_expect_success "usage: incompatible options: $opt and $opt2" '
 			test_incompatible_usage git cat-file $opt $opt2
 		'
 	done
-
-	opt2="--path=foo HEAD:some-path.txt"
-	test_expect_success "usage: incompatible options: $opt and $opt2" '
-		test_incompatible_usage git cat-file $opt $opt2
-	'
 done
 
+test_too_many_arguments() {
+	test_expect_code 129 "$@" 2>err &&
+	grep -E "^fatal: too many arguments$" err
+}
+
 for opt in $short_modes $cw_modes
 do
 	args="one two three"
 	test_expect_success "usage: too many arguments: $opt $args" '
-		test_expect_code 129 git cat-file $opt $args
+		test_too_many_arguments git cat-file $opt $args
 	'
 
 	for opt2 in --buffer --follow-symlinks
 	do
 		test_expect_success "usage: incompatible arguments: $opt with batch option $opt2" '
-			test_expect_code 129 git cat-file $opt $opt2
+			test_incompatible_usage git cat-file $opt $opt2
 		'
 	done
 done
@@ -84,14 +90,9 @@  for opt in --buffer \
 	--follow-symlinks \
 	--batch-all-objects
 do
-	status=success
-	if test $opt = "--buffer"
-	then
-		status=failure
-	fi
-	test_expect_$status "usage: bad option combination: $opt without batch mode" '
-		test_expect_code 129 git cat-file $opt &&
-		test_expect_code 129 git cat-file $opt commit HEAD
+	test_expect_success "usage: bad option combination: $opt without batch mode" '
+		test_incompatible_usage git cat-file $opt &&
+		test_incompatible_usage git cat-file $opt commit HEAD
 	'
 done