diff --git a/Documentation/git-rev-parse.txt b/Documentation/git-rev-parse.txt index f26a7591e3737d..6a4968f68a3c9d 100644 --- a/Documentation/git-rev-parse.txt +++ b/Documentation/git-rev-parse.txt @@ -398,7 +398,7 @@ some-command [] ... some-command does foo and bar! -- -h,help show the help +h,help! show the help foo some nifty option --foo bar= some cool option --bar with an argument @@ -424,10 +424,10 @@ usage: some-command [] ... some-command does foo and bar! -h, --help show the help - --foo some nifty option --foo - --bar ... some cool option --bar with an argument - --baz another cool option --baz with a named argument - --qux[=] qux may take a path argument but has meaning by itself + --[no-]foo some nifty option --foo + --[no-]bar ... some cool option --bar with an argument + --[no-]baz another cool option --baz with a named argument + --[no-]qux[=] qux may take a path argument but has meaning by itself An option group Header -C[...] option C with an optional argument diff --git a/contrib/subtree/git-subtree.sh b/contrib/subtree/git-subtree.sh index 7db4c45676d304..e0c5d3b0de6348 100755 --- a/contrib/subtree/git-subtree.sh +++ b/contrib/subtree/git-subtree.sh @@ -33,19 +33,19 @@ git subtree split --prefix= [] git subtree pull --prefix= git subtree push --prefix= -- -h,help show the help -q,quiet quiet -d,debug show debug messages +h,help! show the help +q,quiet! quiet +d,debug! show debug messages P,prefix= the name of the subdir to split out options for 'split' (also: 'push') annotate= add a prefix to commit message of new commits -b,branch= create a new branch from the split subtree +b,branch!= create a new branch from the split subtree ignore-joins ignore prior --rejoin commits onto= try connecting new tree to an existing one rejoin merge the new branch back into HEAD options for 'add' and 'merge' (also: 'pull', 'split --rejoin', and 'push --rejoin') squash merge subtree changes as a single commit -m,message= use the given message as the commit message for the merge commit +m,message!= use the given message as the commit message for the merge commit " indent=0 diff --git a/contrib/subtree/t/t7900-subtree.sh b/contrib/subtree/t/t7900-subtree.sh index 341c169eca7e6c..49a21dd7c9c553 100755 --- a/contrib/subtree/t/t7900-subtree.sh +++ b/contrib/subtree/t/t7900-subtree.sh @@ -71,7 +71,7 @@ test_expect_success 'shows short help text for -h' ' test_expect_code 129 git subtree -h >out 2>err && test_must_be_empty err && grep -e "^ *or: git subtree pull" out && - grep -e --annotate out + grep -F -e "--[no-]annotate" out ' # diff --git a/parse-options.c b/parse-options.c index 60224cf8d03fb1..76d2e76b492aff 100644 --- a/parse-options.c +++ b/parse-options.c @@ -1023,14 +1023,37 @@ static int usage_argh(const struct option *opts, FILE *outfile) return utf8_fprintf(outfile, s, opts->argh ? _(opts->argh) : _("...")); } -#define USAGE_OPTS_WIDTH 24 -#define USAGE_GAP 2 +static int usage_indent(FILE *outfile) +{ + return fprintf(outfile, " "); +} + +#define USAGE_OPTS_WIDTH 26 + +static void usage_padding(FILE *outfile, size_t pos) +{ + if (pos < USAGE_OPTS_WIDTH) + fprintf(outfile, "%*s", USAGE_OPTS_WIDTH - (int)pos, ""); + else + fprintf(outfile, "\n%*s", USAGE_OPTS_WIDTH, ""); +} + +static const struct option *find_option_by_long_name(const struct option *opts, + const char *long_name) +{ + for (; opts->type != OPTION_END; opts++) { + if (opts->long_name && !strcmp(opts->long_name, long_name)) + return opts; + } + return NULL; +} static enum parse_opt_result usage_with_options_internal(struct parse_opt_ctx_t *ctx, const char * const *usagestr, const struct option *opts, int full, int err) { + const struct option *all_opts = opts; FILE *outfile = err ? stderr : stdout; int need_newline; @@ -1111,8 +1134,8 @@ static enum parse_opt_result usage_with_options_internal(struct parse_opt_ctx_t for (; opts->type != OPTION_END; opts++) { size_t pos; - int pad; const char *cp, *np; + const char *positive_name = NULL; if (opts->type == OPTION_SUBCOMMAND) continue; @@ -1131,7 +1154,7 @@ static enum parse_opt_result usage_with_options_internal(struct parse_opt_ctx_t need_newline = 0; } - pos = fprintf(outfile, " "); + pos = usage_indent(outfile); if (opts->short_name) { if (opts->flags & PARSE_OPT_NODASH) pos += fprintf(outfile, "%c", opts->short_name); @@ -1140,8 +1163,15 @@ static enum parse_opt_result usage_with_options_internal(struct parse_opt_ctx_t } if (opts->long_name && opts->short_name) pos += fprintf(outfile, ", "); - if (opts->long_name) - pos += fprintf(outfile, "--%s", opts->long_name); + if (opts->long_name) { + const char *long_name = opts->long_name; + if ((opts->flags & PARSE_OPT_NONEG) || + skip_prefix(long_name, "no-", &positive_name)) + pos += fprintf(outfile, "--%s", long_name); + else + pos += fprintf(outfile, "--[no-]%s", long_name); + } + if (opts->type == OPTION_NUMBER) pos += utf8_fprintf(outfile, _("-NUM")); @@ -1149,16 +1179,8 @@ static enum parse_opt_result usage_with_options_internal(struct parse_opt_ctx_t !(opts->flags & PARSE_OPT_NOARG)) pos += usage_argh(opts, outfile); - if (pos == USAGE_OPTS_WIDTH + 1) - pad = -1; - else if (pos <= USAGE_OPTS_WIDTH) - pad = USAGE_OPTS_WIDTH - pos; - else { - fputc('\n', outfile); - pad = USAGE_OPTS_WIDTH; - } if (opts->type == OPTION_ALIAS) { - fprintf(outfile, "%*s", pad + USAGE_GAP, ""); + usage_padding(outfile, pos); fprintf_ln(outfile, _("alias of --%s"), (const char *)opts->value); continue; @@ -1166,12 +1188,21 @@ static enum parse_opt_result usage_with_options_internal(struct parse_opt_ctx_t for (cp = _(opts->help); *cp; cp = np) { np = strchrnul(cp, '\n'); - fprintf(outfile, - "%*s%.*s\n", pad + USAGE_GAP, "", - (int)(np - cp), cp); + usage_padding(outfile, pos); + fprintf(outfile, "%.*s\n", (int)(np - cp), cp); if (*np) np++; - pad = USAGE_OPTS_WIDTH; + pos = 0; + } + + if (positive_name) { + if (find_option_by_long_name(all_opts, positive_name)) + continue; + pos = usage_indent(outfile); + pos += fprintf(outfile, "--%s", positive_name); + usage_padding(outfile, pos); + fprintf_ln(outfile, _("opposite of --no-%s"), + positive_name); } } fputc('\n', outfile); diff --git a/t/t0040-parse-options.sh b/t/t0040-parse-options.sh index e19a19963697a0..a0ad6192d64ee6 100755 --- a/t/t0040-parse-options.sh +++ b/t/t0040-parse-options.sh @@ -13,30 +13,35 @@ usage: test-tool parse-options A helper function for the parse-options API. - --yes get a boolean + --[no-]yes get a boolean -D, --no-doubt begins with 'no-' + --doubt opposite of --no-doubt -B, --no-fear be brave - -b, --boolean increment by one - -4, --or4 bitwise-or boolean with ...0100 - --neg-or4 same as --no-or4 + -b, --[no-]boolean increment by one + -4, --[no-]or4 bitwise-or boolean with ...0100 + --[no-]neg-or4 same as --no-or4 - -i, --integer get a integer + -i, --[no-]integer + get a integer -j get a integer, too -m, --magnitude get a magnitude - --set23 set integer to 23 + --[no-]set23 set integer to 23 --mode1 set integer to 1 (cmdmode option) --mode2 set integer to 2 (cmdmode option) - -L, --length get length of - -F, --file set file to + -L, --[no-]length + get length of + -F, --[no-]file + set file to String options - -s, --string get a string - --string2 get another string - --st get another string (pervert ordering) + -s, --[no-]string + get a string + --[no-]string2 get another string + --[no-]st get another string (pervert ordering) -o get another string --longhelp help text of this entry spans multiple lines - --list add str to list + --[no-]list add str to list Magic arguments -NUM set integer to NUM @@ -45,16 +50,17 @@ Magic arguments --no-ambiguous negative ambiguity Standard options - --abbrev[=] use digits to display object names - -v, --verbose be verbose - -n, --dry-run dry run - -q, --quiet be quiet - --expect expected output in the variable dump + --[no-]abbrev[=] use digits to display object names + -v, --[no-]verbose be verbose + -n, --[no-]dry-run dry run + -q, --[no-]quiet be quiet + --[no-]expect + expected output in the variable dump Alias - -A, --alias-source + -A, --[no-]alias-source get a string - -Z, --alias-target + -Z, --[no-]alias-target alias of --alias-source EOF diff --git a/t/t1502-rev-parse-parseopt.sh b/t/t1502-rev-parse-parseopt.sh index dd811b7fb467c4..f0737593c3fda7 100755 --- a/t/t1502-rev-parse-parseopt.sh +++ b/t/t1502-rev-parse-parseopt.sh @@ -3,13 +3,29 @@ test_description='test git rev-parse --parseopt' . ./test-lib.sh +check_invalid_long_option () { + spec="$1" + opt="$2" + test_expect_success "test --parseopt invalid switch $opt help output for $spec" ' + { + cat <<-\EOF && + error: unknown option `'${opt#--}\'' + EOF + sed -e 1d -e \$d <"$TEST_DIRECTORY/t1502/$spec.help" + } >expect && + test_expect_code 129 git rev-parse --parseopt -- $opt \ + 2>output <"$TEST_DIRECTORY/t1502/$spec" && + test_cmp expect output + ' +} + test_expect_success 'setup optionspec' ' sed -e "s/^|//" >optionspec <<\EOF |some-command [options] ... | |some-command does foo and bar! |-- -|h,help show the help +|h,help! show the help | |foo some nifty option --foo |bar= some cool option --bar with an argument @@ -58,44 +74,8 @@ EOF ' test_expect_success 'test --parseopt help output' ' - sed -e "s/^|//" >expect <<\END_EXPECT && -|cat <<\EOF -|usage: some-command [options] ... -| -| some-command does foo and bar! -| -| -h, --help show the help -| --foo some nifty option --foo -| --bar ... some cool option --bar with an argument -| -b, --baz a short and long option -| -|An option group Header -| -C[...] option C with an optional argument -| -d, --data[=...] short and long option with an optional argument -| -|Argument hints -| -B short option required argument -| --bar2 long option required argument -| -e, --fuz -| short and long option required argument -| -s[] short option optional argument -| --long[=] long option optional argument -| -g, --fluf[=] short and long option optional argument -| --longest -| a very long argument hint -| --pair with an equals sign in the hint -| --aswitch help te=t contains? fl*g characters!` -| --bswitch hint has trailing tab character -| --cswitch switch has trailing tab character -| --short-hint with a one symbol hint -| -|Extras -| --extra1 line above used to cause a segfault but no longer does -| -|EOF -END_EXPECT test_expect_code 129 git rev-parse --parseopt -- -h > output < optionspec && - test_cmp expect output + test_cmp "$TEST_DIRECTORY/t1502/optionspec.help" output ' test_expect_success 'test --parseopt help output no switches' ' @@ -131,7 +111,7 @@ test_expect_success 'test --parseopt help-all output hidden switches' ' | | some-command does foo and bar! | -| --hidden1 A hidden switch +| --[no-]hidden1 A hidden switch | |EOF END_EXPECT @@ -140,41 +120,12 @@ END_EXPECT ' test_expect_success 'test --parseopt invalid switch help output' ' - sed -e "s/^|//" >expect <<\END_EXPECT && -|error: unknown option `does-not-exist'\'' -|usage: some-command [options] ... -| -| some-command does foo and bar! -| -| -h, --help show the help -| --foo some nifty option --foo -| --bar ... some cool option --bar with an argument -| -b, --baz a short and long option -| -|An option group Header -| -C[...] option C with an optional argument -| -d, --data[=...] short and long option with an optional argument -| -|Argument hints -| -B short option required argument -| --bar2 long option required argument -| -e, --fuz -| short and long option required argument -| -s[] short option optional argument -| --long[=] long option optional argument -| -g, --fluf[=] short and long option optional argument -| --longest -| a very long argument hint -| --pair with an equals sign in the hint -| --aswitch help te=t contains? fl*g characters!` -| --bswitch hint has trailing tab character -| --cswitch switch has trailing tab character -| --short-hint with a one symbol hint -| -|Extras -| --extra1 line above used to cause a segfault but no longer does -| -END_EXPECT + { + cat <<-\EOF && + error: unknown option `does-not-exist'\'' + EOF + sed -e 1d -e \$d <"$TEST_DIRECTORY/t1502/optionspec.help" + } >expect && test_expect_code 129 git rev-parse --parseopt -- --does-not-exist 1>/dev/null 2>output < optionspec && test_cmp expect output ' @@ -288,7 +239,7 @@ test_expect_success 'test --parseopt help output: "wrapped" options normal "or:" | [--another-option] |cmd [--yet-another-option] |-- - |h,help show the help + |h,help! show the help EOF sed -e "s/^|//" >expect <<-\END_EXPECT && @@ -322,7 +273,7 @@ test_expect_success 'test --parseopt help output: multi-line blurb after empty l |line |blurb |-- - |h,help show the help + |h,help! show the help EOF sed -e "s/^|//" >expect <<-\END_EXPECT && @@ -343,4 +294,32 @@ test_expect_success 'test --parseopt help output: multi-line blurb after empty l test_cmp expect actual ' +test_expect_success 'test --parseopt help output for optionspec-neg' ' + test_expect_code 129 git rev-parse --parseopt -- \ + -h >output <"$TEST_DIRECTORY/t1502/optionspec-neg" && + test_cmp "$TEST_DIRECTORY/t1502/optionspec-neg.help" output +' + +test_expect_success 'test --parseopt valid options for optionspec-neg' ' + cat >expect <<-\EOF && + set -- --foo --no-foo --no-bar --positive-only --no-negative -- + EOF + git rev-parse --parseopt -- <"$TEST_DIRECTORY/t1502/optionspec-neg" >output \ + --foo --no-foo --no-bar --positive-only --no-negative && + test_cmp expect output +' + +test_expect_success 'test --parseopt positivated option for optionspec-neg' ' + cat >expect <<-\EOF && + set -- --no-no-bar --no-no-bar -- + EOF + git rev-parse --parseopt -- <"$TEST_DIRECTORY/t1502/optionspec-neg" >output \ + --no-no-bar --bar && + test_cmp expect output +' + +check_invalid_long_option optionspec-neg --no-positive-only +check_invalid_long_option optionspec-neg --negative +check_invalid_long_option optionspec-neg --no-no-negative + test_done diff --git a/t/t1502/.gitattributes b/t/t1502/.gitattributes new file mode 100644 index 00000000000000..562b12e16ebc54 --- /dev/null +++ b/t/t1502/.gitattributes @@ -0,0 +1 @@ +* -whitespace diff --git a/t/t1502/optionspec-neg b/t/t1502/optionspec-neg new file mode 100644 index 00000000000000..392f43eb0b9357 --- /dev/null +++ b/t/t1502/optionspec-neg @@ -0,0 +1,8 @@ +some-command [options] ... + +some-command does foo and bar! +-- +foo can be negated +no-bar can be positivated +positive-only! cannot be negated +no-negative! cannot be positivated diff --git a/t/t1502/optionspec-neg.help b/t/t1502/optionspec-neg.help new file mode 100644 index 00000000000000..7a29f8cb03374e --- /dev/null +++ b/t/t1502/optionspec-neg.help @@ -0,0 +1,12 @@ +cat <<\EOF +usage: some-command [options] ... + + some-command does foo and bar! + + --[no-]foo can be negated + --no-bar can be positivated + --bar opposite of --no-bar + --positive-only cannot be negated + --no-negative cannot be positivated + +EOF diff --git a/t/t1502/optionspec.help b/t/t1502/optionspec.help new file mode 100755 index 00000000000000..cbdd54d41b7be0 --- /dev/null +++ b/t/t1502/optionspec.help @@ -0,0 +1,36 @@ +cat <<\EOF +usage: some-command [options] ... + + some-command does foo and bar! + + -h, --help show the help + --[no-]foo some nifty option --foo + --[no-]bar ... some cool option --bar with an argument + -b, --[no-]baz a short and long option + +An option group Header + -C[...] option C with an optional argument + -d, --[no-]data[=...] short and long option with an optional argument + +Argument hints + -B short option required argument + --[no-]bar2 long option required argument + -e, --[no-]fuz + short and long option required argument + -s[] short option optional argument + --[no-]long[=] long option optional argument + -g, --[no-]fluf[=] + short and long option optional argument + --[no-]longest + a very long argument hint + --[no-]pair + with an equals sign in the hint + --[no-]aswitch help te=t contains? fl*g characters!` + --[no-]bswitch hint has trailing tab character + --[no-]cswitch switch has trailing tab character + --[no-]short-hint with a one symbol hint + +Extras + --[no-]extra1 line above used to cause a segfault but no longer does + +EOF