Imported Upstream version 2.20.2
[platform/upstream/git.git] / parse-options.c
index c1c66bd..6932eaf 100644 (file)
@@ -1,32 +1,25 @@
 #include "git-compat-util.h"
 #include "parse-options.h"
 #include "cache.h"
+#include "config.h"
 #include "commit.h"
 #include "color.h"
-
-static int parse_options_usage(struct parse_opt_ctx_t *ctx,
-                              const char * const *usagestr,
-                              const struct option *opts, int err);
+#include "utf8.h"
 
 #define OPT_SHORT 1
 #define OPT_UNSET 2
 
 int optbug(const struct option *opt, const char *reason)
 {
-       if (opt->long_name)
+       if (opt->long_name) {
+               if (opt->short_name)
+                       return error("BUG: switch '%c' (--%s) %s",
+                                    opt->short_name, opt->long_name, reason);
                return error("BUG: option '%s' %s", opt->long_name, reason);
+       }
        return error("BUG: switch '%c' %s", opt->short_name, reason);
 }
 
-int opterror(const struct option *opt, const char *reason, int flags)
-{
-       if (flags & OPT_SHORT)
-               return error("switch `%c' %s", opt->short_name, reason);
-       if (flags & OPT_UNSET)
-               return error("option `no-%s' %s", opt->long_name, reason);
-       return error("option `%s' %s", opt->long_name, reason);
-}
-
 static int get_arg(struct parse_opt_ctx_t *p, const struct option *opt,
                   int flags, const char **arg)
 {
@@ -48,11 +41,45 @@ static void fix_filename(const char *prefix, const char **file)
        if (!file || !*file || !prefix || is_absolute_path(*file)
            || !strcmp("-", *file))
                return;
-       *file = xstrdup(prefix_filename(prefix, strlen(prefix), *file));
+       *file = prefix_filename(prefix, *file);
+}
+
+static int opt_command_mode_error(const struct option *opt,
+                                 const struct option *all_opts,
+                                 int flags)
+{
+       const struct option *that;
+       struct strbuf message = STRBUF_INIT;
+       struct strbuf that_name = STRBUF_INIT;
+
+       /*
+        * Find the other option that was used to set the variable
+        * already, and report that this is not compatible with it.
+        */
+       for (that = all_opts; that->type != OPTION_END; that++) {
+               if (that == opt ||
+                   that->type != OPTION_CMDMODE ||
+                   that->value != opt->value ||
+                   that->defval != *(int *)opt->value)
+                       continue;
+
+               if (that->long_name)
+                       strbuf_addf(&that_name, "--%s", that->long_name);
+               else
+                       strbuf_addf(&that_name, "-%c", that->short_name);
+               strbuf_addf(&message, ": incompatible with %s", that_name.buf);
+               strbuf_release(&that_name);
+               opterror(opt, message.buf, flags);
+               strbuf_release(&message);
+               return -1;
+       }
+       return opterror(opt, ": incompatible with something else", flags);
 }
 
 static int get_value(struct parse_opt_ctx_t *p,
-                    const struct option *opt, int flags)
+                    const struct option *opt,
+                    const struct option *all_opts,
+                    int flags)
 {
        const char *s, *arg;
        const int unset = flags & OPT_UNSET;
@@ -84,6 +111,8 @@ static int get_value(struct parse_opt_ctx_t *p,
                return 0;
 
        case OPTION_COUNTUP:
+               if (*(int *)opt->value < 0)
+                       *(int *)opt->value = 0;
                *(int *)opt->value = unset ? 0 : *(int *)opt->value + 1;
                return 0;
 
@@ -91,8 +120,14 @@ static int get_value(struct parse_opt_ctx_t *p,
                *(int *)opt->value = unset ? 0 : opt->defval;
                return 0;
 
-       case OPTION_SET_PTR:
-               *(void **)opt->value = unset ? NULL : (void *)opt->defval;
+       case OPTION_CMDMODE:
+               /*
+                * Giving the same mode option twice, although is unnecessary,
+                * is not a grave error, so let it pass.
+                */
+               if (*(int *)opt->value && *(int *)opt->value != opt->defval)
+                       return opt_command_mode_error(opt, all_opts, flags);
+               *(int *)opt->value = opt->defval;
                return 0;
 
        case OPTION_STRING:
@@ -144,6 +179,23 @@ static int get_value(struct parse_opt_ctx_t *p,
                        return opterror(opt, "expects a numerical value", flags);
                return 0;
 
+       case OPTION_MAGNITUDE:
+               if (unset) {
+                       *(unsigned long *)opt->value = 0;
+                       return 0;
+               }
+               if (opt->flags & PARSE_OPT_OPTARG && !p->opt) {
+                       *(unsigned long *)opt->value = opt->defval;
+                       return 0;
+               }
+               if (get_arg(p, opt, flags, &arg))
+                       return -1;
+               if (!git_parse_ulong(arg, opt->value))
+                       return opterror(opt,
+                               "expects a non-negative integer value with an optional k/m/g suffix",
+                               flags);
+               return 0;
+
        default:
                die("should not happen, someone must be hit on the forehead");
        }
@@ -151,12 +203,13 @@ static int get_value(struct parse_opt_ctx_t *p,
 
 static int parse_short_opt(struct parse_opt_ctx_t *p, const struct option *options)
 {
+       const struct option *all_opts = options;
        const struct option *numopt = NULL;
 
        for (; options->type != OPTION_END; options++) {
                if (options->short_name == *p->opt) {
                        p->opt = p->opt[1] ? p->opt + 1 : NULL;
-                       return get_value(p, options, OPT_SHORT);
+                       return get_value(p, options, all_opts, OPT_SHORT);
                }
 
                /*
@@ -185,13 +238,11 @@ static int parse_short_opt(struct parse_opt_ctx_t *p, const struct option *optio
 static int parse_long_opt(struct parse_opt_ctx_t *p, const char *arg,
                           const struct option *options)
 {
-       const char *arg_end = strchr(arg, '=');
+       const struct option *all_opts = options;
+       const char *arg_end = strchrnul(arg, '=');
        const struct option *abbrev_option = NULL, *ambiguous_option = NULL;
        int abbrev_flags = 0, ambiguous_flags = 0;
 
-       if (!arg_end)
-               arg_end = arg + strlen(arg);
-
        for (; options->type != OPTION_END; options++) {
                const char *rest, *long_name = options->long_name;
                int flags = 0, opt_flags = 0;
@@ -200,7 +251,8 @@ static int parse_long_opt(struct parse_opt_ctx_t *p, const char *arg,
                        continue;
 
 again:
-               rest = skip_prefix(arg, long_name);
+               if (!skip_prefix(arg, long_name, &rest))
+                       rest = NULL;
                if (options->type == OPTION_ARGUMENT) {
                        if (!rest)
                                continue;
@@ -235,13 +287,13 @@ is_abbreviated:
                        if (options->flags & PARSE_OPT_NONEG)
                                continue;
                        /* negated and abbreviated very much? */
-                       if (!prefixcmp("no-", arg)) {
+                       if (starts_with("no-", arg)) {
                                flags |= OPT_UNSET;
                                goto is_abbreviated;
                        }
                        /* negated? */
-                       if (prefixcmp(arg, "no-")) {
-                               if (!prefixcmp(long_name, "no-")) {
+                       if (!starts_with(arg, "no-")) {
+                               if (starts_with(long_name, "no-")) {
                                        long_name += 3;
                                        opt_flags |= OPT_UNSET;
                                        goto again;
@@ -249,42 +301,47 @@ is_abbreviated:
                                continue;
                        }
                        flags |= OPT_UNSET;
-                       rest = skip_prefix(arg + 3, long_name);
-                       /* abbreviated and negated? */
-                       if (!rest && !prefixcmp(long_name, arg + 3))
-                               goto is_abbreviated;
-                       if (!rest)
-                               continue;
+                       if (!skip_prefix(arg + 3, long_name, &rest)) {
+                               /* abbreviated and negated? */
+                               if (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);
+               return get_value(p, options, all_opts, flags ^ opt_flags);
        }
 
-       if (ambiguous_option)
-               return error("Ambiguous option: %s "
+       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 -3;
+       }
        if (abbrev_option)
-               return get_value(p, abbrev_option, abbrev_flags);
+               return get_value(p, abbrev_option, all_opts, abbrev_flags);
        return -2;
 }
 
 static int parse_nodash_opt(struct parse_opt_ctx_t *p, const char *arg,
                            const struct option *options)
 {
+       const struct option *all_opts = options;
+
        for (; options->type != OPTION_END; options++) {
                if (!(options->flags & PARSE_OPT_NODASH))
                        continue;
                if (options->short_name == arg[0] && arg[1] == '\0')
-                       return get_value(p, options, OPT_SHORT);
+                       return get_value(p, options, all_opts, OPT_SHORT);
        }
        return -2;
 }
@@ -294,7 +351,7 @@ static void check_typos(const char *arg, const struct option *options)
        if (strlen(arg) < 3)
                return;
 
-       if (!prefixcmp(arg, "no-")) {
+       if (starts_with(arg, "no-")) {
                error ("did you mean `--%s` (with two dashes ?)", arg);
                exit(129);
        }
@@ -302,7 +359,7 @@ static void check_typos(const char *arg, const struct option *options)
        for (; options->type != OPTION_END; options++) {
                if (!options->long_name)
                        continue;
-               if (!prefixcmp(options->long_name, arg)) {
+               if (starts_with(options->long_name, arg)) {
                        error ("did you mean `--%s` (with two dashes ?)", arg);
                        exit(129);
                }
@@ -312,12 +369,20 @@ static void check_typos(const char *arg, const struct option *options)
 static void parse_options_check(const struct option *opts)
 {
        int err = 0;
+       char short_opts[128];
 
+       memset(short_opts, '\0', sizeof(short_opts));
        for (; opts->type != OPTION_END; opts++) {
                if ((opts->flags & PARSE_OPT_LASTARG_DEFAULT) &&
                    (opts->flags & PARSE_OPT_OPTARG))
                        err |= optbug(opts, "uses incompatible flags "
                                        "LASTARG_DEFAULT and OPTARG");
+               if (opts->short_name) {
+                       if (0x7F <= opts->short_name)
+                               err |= optbug(opts, "invalid short name");
+                       else if (short_opts[opts->short_name]++)
+                               err |= optbug(opts, "short name already used");
+               }
                if (opts->flags & PARSE_OPT_NODASH &&
                    ((opts->flags & PARSE_OPT_OPTARG) ||
                     !(opts->flags & PARSE_OPT_NOARG) ||
@@ -330,7 +395,6 @@ static void parse_options_check(const struct option *opts)
                case OPTION_BIT:
                case OPTION_NEGBIT:
                case OPTION_SET_INT:
-               case OPTION_SET_PTR:
                case OPTION_NUMBER:
                        if ((opts->flags & PARSE_OPT_OPTARG) ||
                            !(opts->flags & PARSE_OPT_NOARG))
@@ -338,6 +402,9 @@ static void parse_options_check(const struct option *opts)
                default:
                        ; /* ok. (usually accepts an argument) */
                }
+               if (opts->argh &&
+                   strcspn(opts->argh, " _") != strlen(opts->argh))
+                       err |= optbug(opts, "multi-word argh should use dash to separate words");
        }
        if (err)
                exit(128);
@@ -348,7 +415,7 @@ void parse_options_start(struct parse_opt_ctx_t *ctx,
                         const struct option *options, int flags)
 {
        memset(ctx, 0, sizeof(*ctx));
-       ctx->argc = argc - 1;
+       ctx->argc = ctx->total = argc - 1;
        ctx->argv = argv + 1;
        ctx->out  = argv;
        ctx->prefix = prefix;
@@ -360,6 +427,98 @@ void parse_options_start(struct parse_opt_ctx_t *ctx,
        parse_options_check(options);
 }
 
+static void show_negated_gitcomp(const struct option *opts, int nr_noopts)
+{
+       int printed_dashdash = 0;
+
+       for (; opts->type != OPTION_END; opts++) {
+               int has_unset_form = 0;
+               const char *name;
+
+               if (!opts->long_name)
+                       continue;
+               if (opts->flags & (PARSE_OPT_HIDDEN | PARSE_OPT_NOCOMPLETE))
+                       continue;
+               if (opts->flags & PARSE_OPT_NONEG)
+                       continue;
+
+               switch (opts->type) {
+               case OPTION_STRING:
+               case OPTION_FILENAME:
+               case OPTION_INTEGER:
+               case OPTION_MAGNITUDE:
+               case OPTION_CALLBACK:
+               case OPTION_BIT:
+               case OPTION_NEGBIT:
+               case OPTION_COUNTUP:
+               case OPTION_SET_INT:
+                       has_unset_form = 1;
+                       break;
+               default:
+                       break;
+               }
+               if (!has_unset_form)
+                       continue;
+
+               if (skip_prefix(opts->long_name, "no-", &name)) {
+                       if (nr_noopts < 0)
+                               printf(" --%s", name);
+               } else if (nr_noopts >= 0) {
+                       if (nr_noopts && !printed_dashdash) {
+                               printf(" --");
+                               printed_dashdash = 1;
+                       }
+                       printf(" --no-%s", opts->long_name);
+                       nr_noopts++;
+               }
+       }
+}
+
+static int show_gitcomp(struct parse_opt_ctx_t *ctx,
+                       const struct option *opts)
+{
+       const struct option *original_opts = opts;
+       int nr_noopts = 0;
+
+       for (; opts->type != OPTION_END; opts++) {
+               const char *suffix = "";
+
+               if (!opts->long_name)
+                       continue;
+               if (opts->flags & (PARSE_OPT_HIDDEN | PARSE_OPT_NOCOMPLETE))
+                       continue;
+
+               switch (opts->type) {
+               case OPTION_GROUP:
+                       continue;
+               case OPTION_STRING:
+               case OPTION_FILENAME:
+               case OPTION_INTEGER:
+               case OPTION_MAGNITUDE:
+               case OPTION_CALLBACK:
+                       if (opts->flags & PARSE_OPT_NOARG)
+                               break;
+                       if (opts->flags & PARSE_OPT_OPTARG)
+                               break;
+                       if (opts->flags & PARSE_OPT_LASTARG_DEFAULT)
+                               break;
+                       suffix = "=";
+                       break;
+               default:
+                       break;
+               }
+               if (opts->flags & PARSE_OPT_COMP_ARG)
+                       suffix = "=";
+               if (starts_with(opts->long_name, "no-"))
+                       nr_noopts++;
+               printf(" --%s%s", opts->long_name, suffix);
+       }
+       show_negated_gitcomp(original_opts, -1);
+       show_negated_gitcomp(original_opts, nr_noopts);
+       fputc('\n', stdout);
+       return PARSE_OPT_COMPLETE;
+}
+
 static int usage_with_options_internal(struct parse_opt_ctx_t *,
                                       const char * const *,
                                       const struct option *, int, int);
@@ -385,27 +544,36 @@ int parse_options_step(struct parse_opt_ctx_t *ctx,
                        continue;
                }
 
+               /* lone -h asks for help */
+               if (internal_help && ctx->total == 1 && !strcmp(arg + 1, "h"))
+                       goto show_usage;
+
+               /* lone --git-completion-helper is asked by git-completion.bash */
+               if (ctx->total == 1 && !strcmp(arg + 1, "-git-completion-helper"))
+                       return show_gitcomp(ctx, options);
+
                if (arg[1] != '-') {
                        ctx->opt = arg + 1;
-                       if (internal_help && *ctx->opt == 'h')
-                               return parse_options_usage(ctx, usagestr, options, 0);
                        switch (parse_short_opt(ctx, options)) {
                        case -1:
-                               return parse_options_usage(ctx, usagestr, options, 1);
+                               return PARSE_OPT_ERROR;
                        case -2:
                                if (ctx->opt)
                                        check_typos(arg + 1, options);
+                               if (internal_help && *ctx->opt == 'h')
+                                       goto show_usage;
                                goto unknown;
                        }
                        if (ctx->opt)
                                check_typos(arg + 1, options);
                        while (ctx->opt) {
-                               if (internal_help && *ctx->opt == 'h')
-                                       return parse_options_usage(ctx, usagestr, options, 0);
                                switch (parse_short_opt(ctx, options)) {
                                case -1:
-                                       return parse_options_usage(ctx, usagestr, options, 1);
+                                       return PARSE_OPT_ERROR;
                                case -2:
+                                       if (internal_help && *ctx->opt == 'h')
+                                               goto show_usage;
+
                                        /* fake a short option thing to hide the fact that we may have
                                         * started to parse aggregated stuff
                                         *
@@ -430,12 +598,14 @@ int parse_options_step(struct parse_opt_ctx_t *ctx,
                if (internal_help && !strcmp(arg + 2, "help-all"))
                        return usage_with_options_internal(ctx, usagestr, options, 1, 0);
                if (internal_help && !strcmp(arg + 2, "help"))
-                       return parse_options_usage(ctx, usagestr, options, 0);
+                       goto show_usage;
                switch (parse_long_opt(ctx, arg + 2, options)) {
                case -1:
-                       return parse_options_usage(ctx, usagestr, options, 1);
+                       return PARSE_OPT_ERROR;
                case -2:
                        goto unknown;
+               case -3:
+                       goto show_usage;
                }
                continue;
 unknown:
@@ -445,11 +615,14 @@ unknown:
                ctx->opt = NULL;
        }
        return PARSE_OPT_DONE;
+
+ show_usage:
+       return usage_with_options_internal(ctx, usagestr, options, 0, 0);
 }
 
 int parse_options_end(struct parse_opt_ctx_t *ctx)
 {
-       memmove(ctx->out + ctx->cpidx, ctx->argv, ctx->argc * sizeof(*ctx->out));
+       MOVE_ARRAY(ctx->out + ctx->cpidx, ctx->argv, ctx->argc);
        ctx->out[ctx->cpidx + ctx->argc] = NULL;
        return ctx->cpidx + ctx->argc;
 }
@@ -463,15 +636,21 @@ int parse_options(int argc, const char **argv, const char *prefix,
        parse_options_start(&ctx, argc, argv, prefix, options, flags);
        switch (parse_options_step(&ctx, options, usagestr)) {
        case PARSE_OPT_HELP:
+       case PARSE_OPT_ERROR:
                exit(129);
+       case PARSE_OPT_COMPLETE:
+               exit(0);
        case PARSE_OPT_NON_OPTION:
        case PARSE_OPT_DONE:
                break;
        default: /* PARSE_OPT_UNKNOWN */
                if (ctx.argv[0][1] == '-') {
                        error("unknown option `%s'", ctx.argv[0] + 2);
-               } else {
+               } else if (isascii(*ctx.opt)) {
                        error("unknown switch `%c'", *ctx.opt);
+               } else {
+                       error("unknown non-ascii option in string: `%s'",
+                             ctx.argv[0]);
                }
                usage_with_options(usagestr, options);
        }
@@ -483,7 +662,8 @@ int parse_options(int argc, const char **argv, const char *prefix,
 static int usage_argh(const struct option *opts, FILE *outfile)
 {
        const char *s;
-       int literal = (opts->flags & PARSE_OPT_LITERAL_ARGHELP) || !opts->argh;
+       int literal = (opts->flags & PARSE_OPT_LITERAL_ARGHELP) ||
+               !opts->argh || !!strpbrk(opts->argh, "()<>[]|");
        if (opts->flags & PARSE_OPT_OPTARG)
                if (opts->long_name)
                        s = literal ? "[=%s]" : "[=<%s>]";
@@ -491,7 +671,7 @@ static int usage_argh(const struct option *opts, FILE *outfile)
                        s = literal ? "[%s]" : "[<%s>]";
        else
                s = literal ? " %s" : " <%s>";
-       return fprintf(outfile, s, opts->argh ? _(opts->argh) : _("..."));
+       return utf8_fprintf(outfile, s, opts->argh ? _(opts->argh) : _("..."));
 }
 
 #define USAGE_OPTS_WIDTH 24
@@ -502,6 +682,7 @@ static int usage_with_options_internal(struct parse_opt_ctx_t *ctx,
                                       const struct option *opts, int full, int err)
 {
        FILE *outfile = err ? stderr : stdout;
+       int need_newline;
 
        if (!usagestr)
                return PARSE_OPT_HELP;
@@ -511,19 +692,20 @@ static int usage_with_options_internal(struct parse_opt_ctx_t *ctx,
 
        fprintf_ln(outfile, _("usage: %s"), _(*usagestr++));
        while (*usagestr && **usagestr)
-               /* TRANSLATORS: the colon here should align with the
-                  one in "usage: %s" translation */
+               /*
+                * TRANSLATORS: the colon here should align with the
+                * one in "usage: %s" translation.
+                */
                fprintf_ln(outfile, _("   or: %s"), _(*usagestr++));
        while (*usagestr) {
                if (**usagestr)
                        fprintf_ln(outfile, _("    %s"), _(*usagestr));
                else
-                       putchar('\n');
+                       fputc('\n', outfile);
                usagestr++;
        }
 
-       if (opts->type != OPTION_GROUP)
-               fputc('\n', outfile);
+       need_newline = 1;
 
        for (; opts->type != OPTION_END; opts++) {
                size_t pos;
@@ -531,6 +713,7 @@ static int usage_with_options_internal(struct parse_opt_ctx_t *ctx,
 
                if (opts->type == OPTION_GROUP) {
                        fputc('\n', outfile);
+                       need_newline = 0;
                        if (*opts->help)
                                fprintf(outfile, "%s\n", _(opts->help));
                        continue;
@@ -538,6 +721,11 @@ static int usage_with_options_internal(struct parse_opt_ctx_t *ctx,
                if (!full && (opts->flags & PARSE_OPT_HIDDEN))
                        continue;
 
+               if (need_newline) {
+                       fputc('\n', outfile);
+                       need_newline = 0;
+               }
+
                pos = fprintf(outfile, "    ");
                if (opts->short_name) {
                        if (opts->flags & PARSE_OPT_NODASH)
@@ -550,7 +738,7 @@ static int usage_with_options_internal(struct parse_opt_ctx_t *ctx,
                if (opts->long_name)
                        pos += fprintf(outfile, "--%s", opts->long_name);
                if (opts->type == OPTION_NUMBER)
-                       pos += fprintf(outfile, "-NUM");
+                       pos += utf8_fprintf(outfile, _("-NUM"));
 
                if ((opts->flags & PARSE_OPT_LITERAL_ARGHELP) ||
                    !(opts->flags & PARSE_OPT_NOARG))
@@ -583,14 +771,16 @@ void NORETURN usage_msg_opt(const char *msg,
                   const char * const *usagestr,
                   const struct option *options)
 {
-       fprintf(stderr, "%s\n\n", msg);
+       fprintf(stderr, "fatal: %s\n\n", msg);
        usage_with_options(usagestr, options);
 }
 
-static int parse_options_usage(struct parse_opt_ctx_t *ctx,
-                              const char * const *usagestr,
-                              const struct option *opts, int err)
+#undef opterror
+int opterror(const struct option *opt, const char *reason, int flags)
 {
-       return usage_with_options_internal(ctx, usagestr, opts, 0, err);
+       if (flags & OPT_SHORT)
+               return error("switch `%c' %s", opt->short_name, reason);
+       if (flags & OPT_UNSET)
+               return error("option `no-%s' %s", opt->long_name, reason);
+       return error("option `%s' %s", opt->long_name, reason);
 }
-