From ddf8420b59297b6b40246a33d8a1c760bedc93cc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=86var=20Arnfj=C3=B6r=C3=B0=20Bjarmason?= Date: Tue, 28 Dec 2021 14:28:41 +0100 Subject: cat-file tests: test bad usage MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Stress test the usage emitted when options are combined in ways that isn't supported. Let's test various option combinations, some of these we buggily allow right now. E.g. this reveals a bug in 321459439e1 (cat-file: support --textconv/--filters in batch mode, 2016-09-09) that we'll fix in a subsequent commit. We're supposed to be emitting a relevant message when --batch-all-objects is combined with --textconv or --filters, but we don't. The cases of needing to assign to opt=2 in the "opt" loop are because on those we do the right thing already, in subsequent commits the "test_expect_failure" cases will be fixed, and the for-loops unified. Signed-off-by: Ævar Arnfjörð Bjarmason Signed-off-by: Junio C Hamano diff --git a/t/t1006-cat-file.sh b/t/t1006-cat-file.sh index 6586283..fc9191c 100755 --- a/t/t1006-cat-file.sh +++ b/t/t1006-cat-file.sh @@ -4,6 +4,100 @@ test_description='git cat-file' . ./test-lib.sh +test_cmdmode_usage () { + test_expect_code 129 "$@" 2>err && + grep "^error:.*is incompatible with" err +} + +for switches in \ + '-e -p' \ + '-p -t' \ + '-t -s' \ + '-s --textconv' \ + '--textconv --filters' +do + test_expect_success "usage: cmdmode $switches" ' + test_cmdmode_usage git cat-file $switches + ' +done + +test_incompatible_usage () { + test_expect_code 129 "$@" 2>err && + grep -E "^error:.**needs" err +} + +for opt in --batch --batch-check +do + test_expect_success "usage: incompatible options: --path with $opt" ' + test_incompatible_usage git cat-file --path=foo $opt + ' +done + +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_expect_failure "usage: incompatible options: --batch-all-objects with $opt" ' + test_incompatible_usage git cat-file --batch-all-objects $opt + ' +done + +for opt in $short_modes +do + test_expect_success "usage: $opt requires another option" ' + test_expect_code 129 git cat-file $opt + ' + + for opt2 in --batch \ + --batch-check \ + --follow-symlinks + do + test_expect_failure "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 + +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 + ' + + 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 + ' + done +done + +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 + ' +done + echo_without_newline () { printf '%s' "$*" } -- cgit v0.10.2-6-g49f6 From 68c69f90c8e98b305d9effd1d25f6261a30b8e50 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=86var=20Arnfj=C3=B6r=C3=B0=20Bjarmason?= Date: Tue, 28 Dec 2021 14:28:42 +0100 Subject: cat-file tests: test messaging on bad objects/paths MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add tests for the output that's emitted when we disambiguate : in cat-file. This gives us a baseline for improving these messages. For e.g. "git blame" we'll emit: $ git blame HEAD:foo fatal: no such path 'HEAD:foo' in HEAD But cat-file doesn't disambiguate these two cases, and just gives the rather unhelpful: $ git cat-file --textconv HEAD:foo fatal: Not a valid object name HEAD:foo Signed-off-by: Ævar Arnfjörð Bjarmason Signed-off-by: Junio C Hamano diff --git a/t/t8007-cat-file-textconv.sh b/t/t8007-cat-file-textconv.sh index eacd49a..71ea2ac 100755 --- a/t/t8007-cat-file-textconv.sh +++ b/t/t8007-cat-file-textconv.sh @@ -19,6 +19,48 @@ test_expect_success 'setup ' ' GIT_AUTHOR_NAME=Number2 git commit -a -m Second --date="2010-01-01 20:00:00" ' +test_expect_success 'usage: ' ' + cat >expect <<-\EOF && + fatal: Not a valid object name HEAD2 + EOF + test_must_fail git cat-file --textconv HEAD2 2>actual && + test_cmp expect actual +' + +test_expect_success 'usage: :' ' + cat >expect <<-\EOF && + fatal: Not a valid object name HEAD2:two.bin + EOF + test_must_fail git cat-file --textconv HEAD2:two.bin 2>actual && + test_cmp expect actual +' + +test_expect_success 'usage: :' ' + cat >expect <<-\EOF && + fatal: Not a valid object name HEAD:two.bin + EOF + test_must_fail git cat-file --textconv HEAD:two.bin 2>actual && + test_cmp expect actual +' + + +test_expect_success 'usage: with no ' ' + cat >expect <<-\EOF && + fatal: git cat-file --textconv HEAD: must be + EOF + test_must_fail git cat-file --textconv HEAD 2>actual && + test_cmp expect actual +' + + +test_expect_success 'usage: :' ' + cat >expect <<-\EOF && + fatal: Not a valid object name HEAD2:one.bin + EOF + test_must_fail git cat-file --textconv HEAD2:one.bin 2>actual && + test_cmp expect actual +' + cat >expected < Date: Tue, 28 Dec 2021 14:28:43 +0100 Subject: parse-options API: add a usage_msg_optf() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add a usage_msg_optf() as a shorthand for the sort of usage_msg_opt(xstrfmt(...)) used in builtin/stash.c. I'll make more use of this function in builtin/cat-file.c shortly. The disconnect between the "..." and "fmt" is a bit unusual, but it works just fine and this keeps it consistent with usage_msg_opt(), i.e. a caller of it can be moved to usage_msg_optf() and not have to have its arguments re-arranged. Signed-off-by: Ævar Arnfjörð Bjarmason Signed-off-by: Junio C Hamano diff --git a/builtin/stash.c b/builtin/stash.c index 18c812b..c9a0904 100644 --- a/builtin/stash.c +++ b/builtin/stash.c @@ -1811,8 +1811,8 @@ int cmd_stash(int argc, const char **argv, const char *prefix) else if (!strcmp(argv[0], "save")) return !!save_stash(argc, argv, prefix); else if (*argv[0] != '-') - usage_msg_opt(xstrfmt(_("unknown subcommand: %s"), argv[0]), - git_stash_usage, options); + usage_msg_optf(_("unknown subcommand: %s"), + git_stash_usage, options, argv[0]); /* Assume 'stash push' */ strvec_push(&args, "push"); diff --git a/parse-options.c b/parse-options.c index fc5b43f..5a319dd 100644 --- a/parse-options.c +++ b/parse-options.c @@ -1078,3 +1078,16 @@ void NORETURN usage_msg_opt(const char *msg, fprintf(stderr, "fatal: %s\n\n", msg); usage_with_options(usagestr, options); } + +void NORETURN usage_msg_optf(const char * const fmt, + const char * const *usagestr, + const struct option *options, ...) +{ + struct strbuf msg = STRBUF_INIT; + va_list ap; + va_start(ap, options); + strbuf_vaddf(&msg, fmt, ap); + va_end(ap); + + usage_msg_opt(msg.buf, usagestr, options); +} diff --git a/parse-options.h b/parse-options.h index 275fb44..4a9fa8a 100644 --- a/parse-options.h +++ b/parse-options.h @@ -225,6 +225,16 @@ NORETURN void usage_msg_opt(const char *msg, const char * const *usagestr, const struct option *options); +/** + * usage_msg_optf() is like usage_msg_opt() except that the first + * argument is a format string, and optional format arguments follow + * after the 3rd option. + */ +__attribute__((format (printf,1,4))) +void NORETURN usage_msg_optf(const char *fmt, + const char * const *usagestr, + const struct option *options, ...); + /* * Use these assertions for callbacks that expect to be called with NONEG and * NOARG respectively, and do not otherwise handle the "unset" and "arg" -- cgit v0.10.2-6-g49f6 From 97fe7250753bae050b5d01c74ac0f38abb052845 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=86var=20Arnfj=C3=B6r=C3=B0=20Bjarmason?= Date: Tue, 28 Dec 2021 14:28:44 +0100 Subject: cat-file docs: fix SYNOPSIS and "-h" output MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit There were various inaccuracies in the previous SYNOPSIS output, e.g. "--path" is not something that can optionally go with any options except --textconv or --filters, as the output implied. The opening line of the DESCRIPTION section is also "In its first form[...]", which refers to "git cat-file ", but the SYNOPSIS section wasn't showing that as the first form! That part of the documentation made sense in d83a42f34a6 (Documentation: minor grammatical fixes in git-cat-file.txt, 2009-03-22) when it was introduced, but since then various options that were added have made that intro make no sense in the context it was in. Now the two will match again. The usage output here is not properly aligned on "master" currently, but will be with my in-flight 4631cfc20bd (parse-options: properly align continued usage output, 2021-09-21), so let's indent things correctly in the C code in anticipation of that. Signed-off-by: Ævar Arnfjörð Bjarmason Signed-off-by: Junio C Hamano diff --git a/Documentation/git-cat-file.txt b/Documentation/git-cat-file.txt index 27b27e2..73ebbc7 100644 --- a/Documentation/git-cat-file.txt +++ b/Documentation/git-cat-file.txt @@ -9,8 +9,14 @@ git-cat-file - Provide content or type and size information for repository objec SYNOPSIS -------- [verse] -'git cat-file' (-t [--allow-unknown-type]| -s [--allow-unknown-type]| -e | -p | | --textconv | --filters ) [--path=] -'git cat-file' (--batch[=] | --batch-check[=]) [ --textconv | --filters ] [--follow-symlinks] +'git cat-file' +'git cat-file' (-e | -p) +'git cat-file' ( -t | -s ) [--allow-unknown-type] +'git cat-file' (--batch | --batch-check) [--batch-all-objects] + [--buffer] [--follow-symlinks] [--unordered] + [--textconv | --filters] +'git cat-file' (--textconv | --filters ) + [: | --path= ] DESCRIPTION ----------- diff --git a/builtin/cat-file.c b/builtin/cat-file.c index 86fc032..1df7f79 100644 --- a/builtin/cat-file.c +++ b/builtin/cat-file.c @@ -619,8 +619,14 @@ static int batch_objects(struct batch_options *opt) } static const char * const cat_file_usage[] = { - N_("git cat-file (-t [--allow-unknown-type] | -s [--allow-unknown-type] | -e | -p | | --textconv | --filters) [--path=] "), - N_("git cat-file (--batch[=] | --batch-check[=]) [--follow-symlinks] [--textconv | --filters]"), + N_("git cat-file "), + N_("git cat-file (-e | -p) "), + N_("git cat-file ( -t | -s ) [--allow-unknown-type] "), + N_("git cat-file (--batch | --batch-check) [--batch-all-objects]\n" + " [--buffer] [--follow-symlinks] [--unordered]\n" + " [--textconv | --filters]"), + N_("git cat-file (--textconv | --filters )\n" + " [: | --path= ]"), NULL }; -- cgit v0.10.2-6-g49f6 From 5a40417876c8ec97e1f367a1638a558ec010e3d8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=86var=20Arnfj=C3=B6r=C3=B0=20Bjarmason?= Date: Tue, 28 Dec 2021 14:28:45 +0100 Subject: cat-file: move "usage" variable to cmd_cat_file() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit There's no benefit to defining this at a distance, and it makes the code harder to read as you've got to scroll up to see the usage that corresponds to the options. In subsequent commits I'll make use of usage_msg_opt(), which will be quite noisy if I have to use the long "cat_file_usage" variable, there's no other command being defined in this file, so let's rename it to just "usage". Signed-off-by: Ævar Arnfjörð Bjarmason Signed-off-by: Junio C Hamano diff --git a/builtin/cat-file.c b/builtin/cat-file.c index 1df7f79..6d0f645 100644 --- a/builtin/cat-file.c +++ b/builtin/cat-file.c @@ -618,18 +618,6 @@ static int batch_objects(struct batch_options *opt) return retval; } -static const char * const cat_file_usage[] = { - N_("git cat-file "), - N_("git cat-file (-e | -p) "), - N_("git cat-file ( -t | -s ) [--allow-unknown-type] "), - N_("git cat-file (--batch | --batch-check) [--batch-all-objects]\n" - " [--buffer] [--follow-symlinks] [--unordered]\n" - " [--textconv | --filters]"), - N_("git cat-file (--textconv | --filters )\n" - " [: | --path= ]"), - NULL -}; - static int git_cat_file_config(const char *var, const char *value, void *cb) { if (userdiff_config(var, value) < 0) @@ -664,6 +652,17 @@ int cmd_cat_file(int argc, const char **argv, const char *prefix) struct batch_options batch = {0}; int unknown_type = 0; + const char * const usage[] = { + N_("git cat-file "), + N_("git cat-file (-e | -p) "), + N_("git cat-file ( -t | -s ) [--allow-unknown-type] "), + N_("git cat-file (--batch | --batch-check) [--batch-all-objects]\n" + " [--buffer] [--follow-symlinks] [--unordered]\n" + " [--textconv | --filters]"), + N_("git cat-file (--textconv | --filters )\n" + " [: | --path= ]"), + NULL + }; const struct option options[] = { OPT_GROUP(N_(" can be one of: blob, tree, commit, tag")), OPT_CMDMODE('t', NULL, &opt, N_("show object type"), 't'), @@ -700,7 +699,7 @@ int cmd_cat_file(int argc, const char **argv, const char *prefix) git_config(git_cat_file_config, NULL); batch.buffer_output = -1; - argc = parse_options(argc, argv, prefix, options, cat_file_usage, 0); + argc = parse_options(argc, argv, prefix, options, usage, 0); if (opt) { if (batch.enabled && (opt == 'c' || opt == 'w')) @@ -708,35 +707,35 @@ int cmd_cat_file(int argc, const char **argv, const char *prefix) else if (argc == 1) obj_name = argv[0]; else - usage_with_options(cat_file_usage, options); + usage_with_options(usage, options); } if (!opt && !batch.enabled) { if (argc == 2) { exp_type = argv[0]; obj_name = argv[1]; } else - usage_with_options(cat_file_usage, options); + usage_with_options(usage, options); } if (batch.enabled) { if (batch.cmdmode != opt || argc) - usage_with_options(cat_file_usage, options); + usage_with_options(usage, options); if (batch.cmdmode && batch.all_objects) die("--batch-all-objects cannot be combined with " "--textconv nor with --filters"); } if ((batch.follow_symlinks || batch.all_objects) && !batch.enabled) { - usage_with_options(cat_file_usage, options); + usage_with_options(usage, options); } if (force_path && opt != 'c' && opt != 'w') { error("--path= needs --textconv or --filters"); - usage_with_options(cat_file_usage, options); + usage_with_options(usage, options); } if (force_path && batch.enabled) { error("--path= incompatible with --batch"); - usage_with_options(cat_file_usage, options); + usage_with_options(usage, options); } if (batch.buffer_output < 0) -- cgit v0.10.2-6-g49f6 From 485fd2c3dae9c05b43fe237f402274a59eb68e81 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=86var=20Arnfj=C3=B6r=C3=B0=20Bjarmason?= Date: Tue, 28 Dec 2021 14:28:46 +0100 Subject: cat-file: make --batch-all-objects a CMDMODE MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The usage of OPT_CMDMODE() in "cat-file"[1] was added in parallel with the development of[3] the --batch-all-objects option[4], so we've since grown[5] checks that it can't be combined with other command modes, when it should just be made a top-level command-mode instead. It doesn't combine with --filters, --textconv etc. By giving parse_options() information about what options are mutually exclusive with one another we can get the die() message being removed here for free, we didn't even use that removed message in some cases, e.g. for both of: --batch-all-objects --textconv --batch-all-objects --filters We'd take the "goto usage" in the "if (opt)" branch, and never reach the previous message. Now we'll emit e.g.: $ git cat-file --batch-all-objects --filters error: option `filters' is incompatible with --batch-all-objects 1. b48158ac94c (cat-file: make the options mutually exclusive, 2015-05-03) 2. https://lore.kernel.org/git/xmqqtwspgusf.fsf@gitster.dls.corp.google.com/ 3. https://lore.kernel.org/git/20150622104559.GG14475@peff.net/ 4. 6a951937ae1 (cat-file: add --batch-all-objects option, 2015-06-22) 5. 321459439e1 (cat-file: support --textconv/--filters in batch mode, 2016-09-09) Signed-off-by: Ævar Arnfjörð Bjarmason Signed-off-by: Junio C Hamano diff --git a/builtin/cat-file.c b/builtin/cat-file.c index 6d0f645..8735620 100644 --- a/builtin/cat-file.c +++ b/builtin/cat-file.c @@ -674,6 +674,8 @@ int cmd_cat_file(int argc, const char **argv, const char *prefix) N_("for blob objects, run textconv on object's content"), 'c'), OPT_CMDMODE(0, "filters", &opt, N_("for blob objects, run filters on object's content"), 'w'), + OPT_CMDMODE(0, "batch-all-objects", &opt, + N_("show all objects with --batch or --batch-check"), 'b'), OPT_STRING(0, "path", &force_path, N_("blob"), N_("use a specific path for --textconv/--filters")), OPT_BOOL(0, "allow-unknown-type", &unknown_type, @@ -689,8 +691,6 @@ int cmd_cat_file(int argc, const char **argv, const char *prefix) batch_option_callback), OPT_BOOL(0, "follow-symlinks", &batch.follow_symlinks, N_("follow in-tree symlinks (used with --batch or --batch-check)")), - OPT_BOOL(0, "batch-all-objects", &batch.all_objects, - N_("show all objects with --batch or --batch-check")), OPT_BOOL(0, "unordered", &batch.unordered, N_("do not order --batch-all-objects output")), OPT_END() @@ -699,30 +699,27 @@ int cmd_cat_file(int argc, const char **argv, const char *prefix) git_config(git_cat_file_config, NULL); batch.buffer_output = -1; - argc = parse_options(argc, argv, prefix, options, usage, 0); - if (opt) { + 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); - } - if (!opt && !batch.enabled) { + } else if (!opt && !batch.enabled) { if (argc == 2) { exp_type = argv[0]; obj_name = argv[1]; } else usage_with_options(usage, options); - } - if (batch.enabled) { - if (batch.cmdmode != opt || argc) - usage_with_options(usage, options); - if (batch.cmdmode && batch.all_objects) - die("--batch-all-objects cannot be combined with " - "--textconv nor with --filters"); - } + } else if (batch.enabled && batch.cmdmode != opt) + usage_with_options(usage, options); if ((batch.follow_symlinks || batch.all_objects) && !batch.enabled) { usage_with_options(usage, options); diff --git a/t/t1006-cat-file.sh b/t/t1006-cat-file.sh index fc9191c..ebec206 100755 --- a/t/t1006-cat-file.sh +++ b/t/t1006-cat-file.sh @@ -14,7 +14,8 @@ for switches in \ '-p -t' \ '-t -s' \ '-s --textconv' \ - '--textconv --filters' + '--textconv --filters' \ + '--batch-all-objects -e' do test_expect_success "usage: cmdmode $switches" ' test_cmdmode_usage git cat-file $switches @@ -41,10 +42,6 @@ do test_expect_success "usage: $opt requires another option" ' test_expect_code 129 git cat-file $opt ' - - test_expect_failure "usage: incompatible options: --batch-all-objects with $opt" ' - test_incompatible_usage git cat-file --batch-all-objects $opt - ' done for opt in $short_modes -- cgit v0.10.2-6-g49f6 From b3fe468075b7f0aa40f7a22a5a50e5e3e5fb2148 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=86var=20Arnfj=C3=B6r=C3=B0=20Bjarmason?= Date: Tue, 28 Dec 2021 14:28:47 +0100 Subject: cat-file: fix remaining usage bugs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 Signed-off-by: Junio C Hamano diff --git a/builtin/cat-file.c b/builtin/cat-file.c index 8735620..8952920 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= 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= 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(_(" required with '%s'"), + usage, options, "--textconv"); + else if (!argc && opt == 'w') + usage_msg_optf(_(" required with '%s'"), + usage, options, "--filters"); + else if (!argc && opt_epts) + usage_msg_optf(_(" 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 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 ebec206..aa85927 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 -- cgit v0.10.2-6-g49f6 From 57d6a1cf967e82c5cb51d0950c1ed7d1b0916fe8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=86var=20Arnfj=C3=B6r=C3=B0=20Bjarmason?= Date: Tue, 28 Dec 2021 14:28:48 +0100 Subject: cat-file: correct and improve usage information MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Change the usage output emitted on "git cat-file -h" to group related options, making it clear to users which options go with which other ones. The new output is: Check object existence or emit object contents -e check if exists -p pretty-print content Emit [broken] object attributes -t show object type (one of 'blob', 'tree', 'commit', 'tag', ...) -s show object size --allow-unknown-type allow -s and -t to work with broken/corrupt objects Batch objects requested on stdin (or --batch-all-objects) --batch[=] show full or contents --batch-check[=] like --batch, but don't emit --batch-all-objects with --batch[-check]: ignores stdin, batches all known objects Change or optimize batch output --buffer buffer --batch output --follow-symlinks follow in-tree symlinks --unordered do not order objects before emitting them Emit object (blob or tree) with conversion or filter (stand-alone, or with batch) --textconv run textconv on object's content --filters run filters on object's content --path blob|tree use a for (--textconv | --filters ); Not with 'batch' The old usage was: can be one of: blob, tree, commit, tag -t show object type -s show object size -e exit with zero when there's no error -p pretty-print object's content --textconv for blob objects, run textconv on object's content --filters for blob objects, run filters on object's content --batch-all-objects show all objects with --batch or --batch-check --path use a specific path for --textconv/--filters --allow-unknown-type allow -s and -t to work with broken/corrupt objects --buffer buffer --batch output --batch[=] show info and content of objects fed from the standard input --batch-check[=] show info about objects fed from the standard input --follow-symlinks follow in-tree symlinks (used with --batch or --batch-check) --unordered do not order --batch-all-objects output While shorter, I think the new one is easier to understand, as e.g. "--allow-unknown-type" is grouped with "-t" and "-s", as it can only be combined with those options. The same goes for "--buffer", "--unordered" etc. Signed-off-by: Ævar Arnfjörð Bjarmason Signed-off-by: Junio C Hamano diff --git a/builtin/cat-file.c b/builtin/cat-file.c index 8952920..b5b130d 100644 --- a/builtin/cat-file.c +++ b/builtin/cat-file.c @@ -666,35 +666,44 @@ int cmd_cat_file(int argc, const char **argv, const char *prefix) NULL }; const struct option options[] = { - OPT_GROUP(N_(" can be one of: blob, tree, commit, tag")), - OPT_CMDMODE('t', NULL, &opt, N_("show object type"), 't'), - OPT_CMDMODE('s', NULL, &opt, N_("show object size"), 's'), + /* Simple queries */ + OPT_GROUP(N_("Check object existence or emit object contents")), OPT_CMDMODE('e', NULL, &opt, - N_("exit with zero when there's no error"), 'e'), - OPT_CMDMODE('p', NULL, &opt, N_("pretty-print object's content"), 'p'), - OPT_CMDMODE(0, "textconv", &opt, - N_("for blob objects, run textconv on object's content"), 'c'), - OPT_CMDMODE(0, "filters", &opt, - N_("for blob objects, run filters on object's content"), 'w'), - OPT_CMDMODE(0, "batch-all-objects", &opt, - N_("show all objects with --batch or --batch-check"), 'b'), - OPT_STRING(0, "path", &force_path, N_("blob"), - N_("use a specific path for --textconv/--filters")), + N_("check if exists"), 'e'), + OPT_CMDMODE('p', NULL, &opt, N_("pretty-print content"), 'p'), + + OPT_GROUP(N_("Emit [broken] object attributes")), + OPT_CMDMODE('t', NULL, &opt, N_("show object type (one of 'blob', 'tree', 'commit', 'tag', ...)"), 't'), + OPT_CMDMODE('s', NULL, &opt, N_("show object size"), 's'), OPT_BOOL(0, "allow-unknown-type", &unknown_type, N_("allow -s and -t to work with broken/corrupt objects")), - OPT_BOOL(0, "buffer", &batch.buffer_output, N_("buffer --batch output")), - OPT_CALLBACK_F(0, "batch", &batch, "format", - N_("show info and content of objects fed from the standard input"), + /* Batch mode */ + OPT_GROUP(N_("Batch objects requested on stdin (or --batch-all-objects)")), + OPT_CALLBACK_F(0, "batch", &batch, N_("format"), + N_("show full or contents"), PARSE_OPT_OPTARG | PARSE_OPT_NONEG, batch_option_callback), - OPT_CALLBACK_F(0, "batch-check", &batch, "format", - N_("show info about objects fed from the standard input"), + OPT_CALLBACK_F(0, "batch-check", &batch, N_("format"), + N_("like --batch, but don't emit "), PARSE_OPT_OPTARG | PARSE_OPT_NONEG, batch_option_callback), + OPT_CMDMODE(0, "batch-all-objects", &opt, + N_("with --batch[-check]: ignores stdin, batches all known objects"), 'b'), + /* Batch-specific options */ + OPT_GROUP(N_("Change or optimize batch output")), + OPT_BOOL(0, "buffer", &batch.buffer_output, N_("buffer --batch output")), OPT_BOOL(0, "follow-symlinks", &batch.follow_symlinks, - N_("follow in-tree symlinks (used with --batch or --batch-check)")), + N_("follow in-tree symlinks")), OPT_BOOL(0, "unordered", &batch.unordered, - N_("do not order --batch-all-objects output")), + N_("do not order objects before emitting them")), + /* Textconv options, stand-ole*/ + OPT_GROUP(N_("Emit object (blob or tree) with conversion or filter (stand-alone, or with batch)")), + OPT_CMDMODE(0, "textconv", &opt, + N_("run textconv on object's content"), 'c'), + OPT_CMDMODE(0, "filters", &opt, + N_("run filters on object's content"), 'w'), + OPT_STRING(0, "path", &force_path, N_("blob|tree"), + N_("use a for (--textconv | --filters ); Not with 'batch'")), OPT_END() }; -- cgit v0.10.2-6-g49f6 From 9ce6000cb7a023e16ae1743df760edaae1c5ca1d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=86var=20Arnfj=C3=B6r=C3=B0=20Bjarmason?= Date: Tue, 28 Dec 2021 14:28:49 +0100 Subject: object-name.c: don't have GET_OID_ONLY_TO_DIE imply *_QUIETLY MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Stop having GET_OID_ONLY_TO_DIE imply GET_OID_QUIETLY in get_oid_with_context_1(). The *_DIE flag was added in 33bd598c390 (sha1_name.c: teach lookup context to get_sha1_with_context(), 2012-07-02), and then later tweaked in 7243ffdd78d (get_sha1: avoid repeating ourselves via ONLY_TO_DIE, 2016-09-26). Everything in that commit makes sense, but only for callers that expect to fail in an initial call to get_oid_with_context_1(), e.g. as "git show 0017" does via handle_revision_arg(), and then would like to call get_oid_with_context_1() again via this maybe_die_on_misspelt_object_name() function. In the subsequent commit we'll add a new caller that expects to call this only once, but who would still like to have all the error messaging that GET_OID_ONLY_TO_DIE gives it, in addition to any regular errors. Signed-off-by: Ævar Arnfjörð Bjarmason Signed-off-by: Junio C Hamano diff --git a/object-name.c b/object-name.c index fdff460..d44a8f3 100644 --- a/object-name.c +++ b/object-name.c @@ -1795,9 +1795,6 @@ static enum get_oid_result get_oid_with_context_1(struct repository *repo, const char *cp; int only_to_die = flags & GET_OID_ONLY_TO_DIE; - if (only_to_die) - flags |= GET_OID_QUIETLY; - memset(oc, 0, sizeof(*oc)); oc->mode = S_IFINVALID; strbuf_init(&oc->symlink_path, 0); @@ -1932,7 +1929,7 @@ void maybe_die_on_misspelt_object_name(struct repository *r, { struct object_context oc; struct object_id oid; - get_oid_with_context_1(r, name, GET_OID_ONLY_TO_DIE, + get_oid_with_context_1(r, name, GET_OID_ONLY_TO_DIE | GET_OID_QUIETLY, prefix, &oid, &oc); } -- cgit v0.10.2-6-g49f6 From 245b948815048821e73edc1a2b4224eaa94a8fc0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=86var=20Arnfj=C3=B6r=C3=B0=20Bjarmason?= Date: Tue, 28 Dec 2021 14:28:50 +0100 Subject: cat-file: use GET_OID_ONLY_TO_DIE in --(textconv|filters) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Change the cat_one_file() logic that calls get_oid_with_context() under --textconv and --filters to use the GET_OID_ONLY_TO_DIE flag, thus improving the error messaging emitted when e.g. is missing but is not. To service the "cat-file" use-case we need to introduce a new "GET_OID_REQUIRE_PATH" flag, otherwise it would exit early as soon as a valid "HEAD" was resolved, but in the "cat-file" case being changed we always need a valid revision and path. This arguably makes the ":" and ":" use cases worse, as we won't quote the component at the user anymore, but let's just use the existing logic "git log" et al use for now. We can improve the messaging for those cases as a follow-up for all callers. Signed-off-by: Ævar Arnfjörð Bjarmason Signed-off-by: Junio C Hamano diff --git a/builtin/cat-file.c b/builtin/cat-file.c index b5b130d..ad9b3ee 100644 --- a/builtin/cat-file.c +++ b/builtin/cat-file.c @@ -73,14 +73,17 @@ static int cat_one_file(int opt, const char *exp_type, const char *obj_name, struct object_info oi = OBJECT_INFO_INIT; struct strbuf sb = STRBUF_INIT; unsigned flags = OBJECT_INFO_LOOKUP_REPLACE; + unsigned get_oid_flags = GET_OID_RECORD_PATH | GET_OID_ONLY_TO_DIE; const char *path = force_path; + const int opt_cw = (opt == 'c' || opt == 'w'); + if (!path && opt_cw) + get_oid_flags |= GET_OID_REQUIRE_PATH; if (unknown_type) flags |= OBJECT_INFO_ALLOW_UNKNOWN_TYPE; - if (get_oid_with_context(the_repository, obj_name, - GET_OID_RECORD_PATH, - &oid, &obj_context)) + if (get_oid_with_context(the_repository, obj_name, get_oid_flags, &oid, + &obj_context)) die("Not a valid object name %s", obj_name); if (!path) @@ -112,9 +115,6 @@ static int cat_one_file(int opt, const char *exp_type, const char *obj_name, return !has_object_file(&oid); case 'w': - if (!path) - die("git cat-file --filters %s: must be " - "", obj_name); if (filter_object(path, obj_context.mode, &oid, &buf, &size)) @@ -122,10 +122,6 @@ static int cat_one_file(int opt, const char *exp_type, const char *obj_name, break; case 'c': - if (!path) - die("git cat-file --textconv %s: must be ", - obj_name); - if (textconv_object(the_repository, path, obj_context.mode, &oid, 1, &buf, &size)) break; diff --git a/cache.h b/cache.h index eba1248..788127a 100644 --- a/cache.h +++ b/cache.h @@ -1366,6 +1366,7 @@ struct object_context { #define GET_OID_FOLLOW_SYMLINKS 0100 #define GET_OID_RECORD_PATH 0200 #define GET_OID_ONLY_TO_DIE 04000 +#define GET_OID_REQUIRE_PATH 010000 #define GET_OID_DISAMBIGUATORS \ (GET_OID_COMMIT | GET_OID_COMMITTISH | \ diff --git a/object-name.c b/object-name.c index d44a8f3..92862ee 100644 --- a/object-name.c +++ b/object-name.c @@ -1799,6 +1799,9 @@ static enum get_oid_result get_oid_with_context_1(struct repository *repo, oc->mode = S_IFINVALID; strbuf_init(&oc->symlink_path, 0); ret = get_oid_1(repo, name, namelen, oid, flags); + if (!ret && flags & GET_OID_REQUIRE_PATH) + die(_(": required, only '%s' given"), + name); if (!ret) return ret; /* diff --git a/t/t8007-cat-file-textconv.sh b/t/t8007-cat-file-textconv.sh index 71ea2ac..b067983 100755 --- a/t/t8007-cat-file-textconv.sh +++ b/t/t8007-cat-file-textconv.sh @@ -29,7 +29,7 @@ test_expect_success 'usage: ' ' test_expect_success 'usage: :' ' cat >expect <<-\EOF && - fatal: Not a valid object name HEAD2:two.bin + fatal: invalid object name '\''HEAD2'\''. EOF test_must_fail git cat-file --textconv HEAD2:two.bin 2>actual && test_cmp expect actual @@ -37,7 +37,7 @@ test_expect_success 'usage: :' ' test_expect_success 'usage: :' ' cat >expect <<-\EOF && - fatal: Not a valid object name HEAD:two.bin + fatal: path '\''two.bin'\'' does not exist in '\''HEAD'\'' EOF test_must_fail git cat-file --textconv HEAD:two.bin 2>actual && test_cmp expect actual @@ -46,7 +46,7 @@ test_expect_success 'usage: :' ' test_expect_success 'usage: with no ' ' cat >expect <<-\EOF && - fatal: git cat-file --textconv HEAD: must be + fatal: : required, only '\''HEAD'\'' given EOF test_must_fail git cat-file --textconv HEAD 2>actual && test_cmp expect actual @@ -55,7 +55,7 @@ test_expect_success 'usage: with no ' ' test_expect_success 'usage: :' ' cat >expect <<-\EOF && - fatal: Not a valid object name HEAD2:one.bin + fatal: invalid object name '\''HEAD2'\''. EOF test_must_fail git cat-file --textconv HEAD2:one.bin 2>actual && test_cmp expect actual -- cgit v0.10.2-6-g49f6 From 83dc443439ca0fdc9f99fc1ed623d0749ef637ed Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=86var=20Arnfj=C3=B6r=C3=B0=20Bjarmason?= Date: Mon, 10 Jan 2022 23:08:45 +0100 Subject: cat-file: don't whitespace-pad "(...)" in SYNOPSIS and usage output MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fix up whitespace issues around "(... | ...)" in the SYNOPSIS and usage. These were introduced in ab/cat-file series. See e145efa6059 (Merge branch 'ab/cat-file' into next, 2022-01-05). In particular 57d6a1cf96, 5a40417876 and 97fe7250753 in that series. We'll now correctly emit this usage output: $ git cat-file -h usage: git cat-file or: git cat-file (-e | -p) or: git cat-file (-t | -s) [--allow-unknown-type] [...] Before this the last line of that would be inconsistent with the preceding "(-e | -p)": or: git cat-file ( -t | -s ) [--allow-unknown-type] Reported-by: Jiang Xin Signed-off-by: Ævar Arnfjörð Bjarmason Acked-by: Taylor Blau Signed-off-by: Junio C Hamano diff --git a/Documentation/git-cat-file.txt b/Documentation/git-cat-file.txt index 73ebbc7..bef76f4 100644 --- a/Documentation/git-cat-file.txt +++ b/Documentation/git-cat-file.txt @@ -11,11 +11,11 @@ SYNOPSIS [verse] 'git cat-file' 'git cat-file' (-e | -p) -'git cat-file' ( -t | -s ) [--allow-unknown-type] +'git cat-file' (-t | -s) [--allow-unknown-type] 'git cat-file' (--batch | --batch-check) [--batch-all-objects] [--buffer] [--follow-symlinks] [--unordered] [--textconv | --filters] -'git cat-file' (--textconv | --filters ) +'git cat-file' (--textconv | --filters) [: | --path= ] DESCRIPTION diff --git a/builtin/cat-file.c b/builtin/cat-file.c index ad9b3ee..e364922 100644 --- a/builtin/cat-file.c +++ b/builtin/cat-file.c @@ -653,11 +653,11 @@ int cmd_cat_file(int argc, const char **argv, const char *prefix) const char * const usage[] = { N_("git cat-file "), N_("git cat-file (-e | -p) "), - N_("git cat-file ( -t | -s ) [--allow-unknown-type] "), + N_("git cat-file (-t | -s) [--allow-unknown-type] "), N_("git cat-file (--batch | --batch-check) [--batch-all-objects]\n" " [--buffer] [--follow-symlinks] [--unordered]\n" " [--textconv | --filters]"), - N_("git cat-file (--textconv | --filters )\n" + N_("git cat-file (--textconv | --filters)\n" " [: | --path= ]"), NULL }; @@ -699,7 +699,7 @@ int cmd_cat_file(int argc, const char **argv, const char *prefix) OPT_CMDMODE(0, "filters", &opt, N_("run filters on object's content"), 'w'), OPT_STRING(0, "path", &force_path, N_("blob|tree"), - N_("use a for (--textconv | --filters ); Not with 'batch'")), + N_("use a for (--textconv | --filters); Not with 'batch'")), OPT_END() }; -- cgit v0.10.2-6-g49f6 From 5fb249021cfd1cf937a608610c330152936887e1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=86var=20Arnfj=C3=B6r=C3=B0=20Bjarmason?= Date: Mon, 10 Jan 2022 23:08:46 +0100 Subject: cat-file: s/_/-/ in typo'd usage_msg_optf() message MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fix a typo in my recent 03dc51fe849 (cat-file: fix remaining usage bugs, 2021-10-09). Signed-off-by: Ævar Arnfjörð Bjarmason Acked-by: Taylor Blau Signed-off-by: Junio C Hamano diff --git a/builtin/cat-file.c b/builtin/cat-file.c index e364922..7b3f429 100644 --- a/builtin/cat-file.c +++ b/builtin/cat-file.c @@ -727,7 +727,7 @@ int cmd_cat_file(int argc, const char **argv, const char *prefix) ; else if (batch.follow_symlinks) usage_msg_optf(_("'%s' requires a batch mode"), usage, options, - "--follow_symlinks"); + "--follow-symlinks"); else if (batch.buffer_output >= 0) usage_msg_optf(_("'%s' requires a batch mode"), usage, options, "--buffer"); -- cgit v0.10.2-6-g49f6