#include "git-compat-util.h"
#include "parse-options.h"
#include "cache.h"
+#include "config.h"
#include "commit.h"
#include "color.h"
#include "utf8.h"
-static int parse_options_usage(struct parse_opt_ctx_t *ctx,
- const char * const *usagestr,
- const struct option *opts, int err);
-
#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);
}
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,
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;
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");
}
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;
continue;
}
flags |= OPT_UNSET;
- rest = skip_prefix(arg + 3, long_name);
- /* abbreviated and negated? */
- if (!rest && starts_with(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 != '=')
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, all_opts, abbrev_flags);
return -2;
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) ||
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;
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);
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
*
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:
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;
}
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;
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>]";
const struct option *opts, int full, int err)
{
FILE *outfile = err ? stderr : stdout;
+ int need_newline;
if (!usagestr)
return PARSE_OPT_HELP;
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;
if (opts->type == OPTION_GROUP) {
fputc('\n', outfile);
+ need_newline = 0;
if (*opts->help)
fprintf(outfile, "%s\n", _(opts->help));
continue;
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)
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)
-{
- return usage_with_options_internal(ctx, usagestr, opts, 0, err);
-}
-
#undef opterror
int opterror(const struct option *opt, const char *reason, int flags)
{