Merge branch 'nd/diff-parseopt'

The diff machinery, one of the oldest parts of the system, which
long predates the parse-options API, uses fairly long and complex
handcrafted option parser.  This is being rewritten to use the
parse-options API.

* nd/diff-parseopt:
  diff.c: convert --raw
  diff.c: convert -W|--[no-]function-context
  diff.c: convert -U|--unified
  diff.c: convert -u|-p|--patch
  diff.c: prepare to use parse_options() for parsing
  diff.h: avoid bit fields in struct diff_flags
  diff.h: keep forward struct declarations sorted
  parse-options: allow ll_callback with OPTION_CALLBACK
  parse-options: avoid magic return codes
  parse-options: stop abusing 'callback' for lowlevel callbacks
  parse-options: add OPT_BITOP()
  parse-options: disable option abbreviation with PARSE_OPT_KEEP_UNKNOWN
  parse-options: add one-shot mode
  parse-options.h: remove extern on function prototypes
This commit is contained in:
Junio C Hamano
2019-03-07 09:59:52 +09:00
10 changed files with 319 additions and 159 deletions

View File

@ -36,7 +36,7 @@ endif::git-format-patch[]
-U<n>:: -U<n>::
--unified=<n>:: --unified=<n>::
Generate diffs with <n> lines of context instead of Generate diffs with <n> lines of context instead of
the usual three. the usual three. Implies `--patch`.
ifndef::git-format-patch[] ifndef::git-format-patch[]
Implies `-p`. Implies `-p`.
endif::git-format-patch[] endif::git-format-patch[]

View File

@ -814,7 +814,7 @@ int cmd_blame(int argc, const char **argv, const char *prefix)
* and are only included here to get included in the "-h" * and are only included here to get included in the "-h"
* output: * output:
*/ */
{ OPTION_LOWLEVEL_CALLBACK, 0, "indent-heuristic", NULL, NULL, N_("Use an experimental heuristic to improve diffs"), PARSE_OPT_NOARG, parse_opt_unknown_cb }, { OPTION_LOWLEVEL_CALLBACK, 0, "indent-heuristic", NULL, NULL, N_("Use an experimental heuristic to improve diffs"), PARSE_OPT_NOARG, NULL, 0, parse_opt_unknown_cb },
OPT_BIT(0, "minimal", &xdl_opts, N_("Spend extra cycles to find better match"), XDF_NEED_MINIMAL), OPT_BIT(0, "minimal", &xdl_opts, N_("Spend extra cycles to find better match"), XDF_NEED_MINIMAL),
OPT_STRING('S', NULL, &revs_file, N_("file"), N_("Use revisions from <file> instead of calling git-rev-list")), OPT_STRING('S', NULL, &revs_file, N_("file"), N_("Use revisions from <file> instead of calling git-rev-list")),

View File

@ -113,12 +113,15 @@ static int option_parse_message(const struct option *opt,
return 0; return 0;
} }
static int option_read_message(struct parse_opt_ctx_t *ctx, static enum parse_opt_result option_read_message(struct parse_opt_ctx_t *ctx,
const struct option *opt, int unset) const struct option *opt,
const char *arg_not_used,
int unset)
{ {
struct strbuf *buf = opt->value; struct strbuf *buf = opt->value;
const char *arg; const char *arg;
BUG_ON_OPT_ARG(arg_not_used);
if (unset) if (unset)
BUG("-F cannot be negated"); BUG("-F cannot be negated");
@ -262,7 +265,7 @@ static struct option builtin_merge_options[] = {
option_parse_message), option_parse_message),
{ OPTION_LOWLEVEL_CALLBACK, 'F', "file", &merge_msg, N_("path"), { OPTION_LOWLEVEL_CALLBACK, 'F', "file", &merge_msg, N_("path"),
N_("read message from file"), PARSE_OPT_NONEG, N_("read message from file"), PARSE_OPT_NONEG,
(parse_opt_cb *) option_read_message }, NULL, 0, option_read_message },
OPT__VERBOSITY(&verbosity), OPT__VERBOSITY(&verbosity),
OPT_BOOL(0, "abort", &abort_current_merge, OPT_BOOL(0, "abort", &abort_current_merge,
N_("abort the current in-progress merge")), N_("abort the current in-progress merge")),

View File

@ -848,14 +848,16 @@ static int parse_new_style_cacheinfo(const char *arg,
return 0; return 0;
} }
static int cacheinfo_callback(struct parse_opt_ctx_t *ctx, static enum parse_opt_result cacheinfo_callback(
const struct option *opt, int unset) struct parse_opt_ctx_t *ctx, const struct option *opt,
const char *arg, int unset)
{ {
struct object_id oid; struct object_id oid;
unsigned int mode; unsigned int mode;
const char *path; const char *path;
BUG_ON_OPT_NEG(unset); BUG_ON_OPT_NEG(unset);
BUG_ON_OPT_ARG(arg);
if (!parse_new_style_cacheinfo(ctx->argv[1], &mode, &oid, &path)) { if (!parse_new_style_cacheinfo(ctx->argv[1], &mode, &oid, &path)) {
if (add_cacheinfo(mode, &oid, path, 0)) if (add_cacheinfo(mode, &oid, path, 0))
@ -874,12 +876,14 @@ static int cacheinfo_callback(struct parse_opt_ctx_t *ctx,
return 0; return 0;
} }
static int stdin_cacheinfo_callback(struct parse_opt_ctx_t *ctx, static enum parse_opt_result stdin_cacheinfo_callback(
const struct option *opt, int unset) struct parse_opt_ctx_t *ctx, const struct option *opt,
const char *arg, int unset)
{ {
int *nul_term_line = opt->value; int *nul_term_line = opt->value;
BUG_ON_OPT_NEG(unset); BUG_ON_OPT_NEG(unset);
BUG_ON_OPT_ARG(arg);
if (ctx->argc != 1) if (ctx->argc != 1)
return error("option '%s' must be the last argument", opt->long_name); return error("option '%s' must be the last argument", opt->long_name);
@ -888,12 +892,14 @@ static int stdin_cacheinfo_callback(struct parse_opt_ctx_t *ctx,
return 0; return 0;
} }
static int stdin_callback(struct parse_opt_ctx_t *ctx, static enum parse_opt_result stdin_callback(
const struct option *opt, int unset) struct parse_opt_ctx_t *ctx, const struct option *opt,
const char *arg, int unset)
{ {
int *read_from_stdin = opt->value; int *read_from_stdin = opt->value;
BUG_ON_OPT_NEG(unset); BUG_ON_OPT_NEG(unset);
BUG_ON_OPT_ARG(arg);
if (ctx->argc != 1) if (ctx->argc != 1)
return error("option '%s' must be the last argument", opt->long_name); return error("option '%s' must be the last argument", opt->long_name);
@ -901,13 +907,15 @@ static int stdin_callback(struct parse_opt_ctx_t *ctx,
return 0; return 0;
} }
static int unresolve_callback(struct parse_opt_ctx_t *ctx, static enum parse_opt_result unresolve_callback(
const struct option *opt, int unset) struct parse_opt_ctx_t *ctx, const struct option *opt,
const char *arg, int unset)
{ {
int *has_errors = opt->value; int *has_errors = opt->value;
const char *prefix = startup_info->prefix; const char *prefix = startup_info->prefix;
BUG_ON_OPT_NEG(unset); BUG_ON_OPT_NEG(unset);
BUG_ON_OPT_ARG(arg);
/* consume remaining arguments. */ /* consume remaining arguments. */
*has_errors = do_unresolve(ctx->argc, ctx->argv, *has_errors = do_unresolve(ctx->argc, ctx->argv,
@ -920,13 +928,15 @@ static int unresolve_callback(struct parse_opt_ctx_t *ctx,
return 0; return 0;
} }
static int reupdate_callback(struct parse_opt_ctx_t *ctx, static enum parse_opt_result reupdate_callback(
const struct option *opt, int unset) struct parse_opt_ctx_t *ctx, const struct option *opt,
const char *arg, int unset)
{ {
int *has_errors = opt->value; int *has_errors = opt->value;
const char *prefix = startup_info->prefix; const char *prefix = startup_info->prefix;
BUG_ON_OPT_NEG(unset); BUG_ON_OPT_NEG(unset);
BUG_ON_OPT_ARG(arg);
/* consume remaining arguments. */ /* consume remaining arguments. */
setup_work_tree(); setup_work_tree();
@ -986,7 +996,8 @@ int cmd_update_index(int argc, const char **argv, const char *prefix)
N_("add the specified entry to the index"), N_("add the specified entry to the index"),
PARSE_OPT_NOARG | /* disallow --cacheinfo=<mode> form */ PARSE_OPT_NOARG | /* disallow --cacheinfo=<mode> form */
PARSE_OPT_NONEG | PARSE_OPT_LITERAL_ARGHELP, PARSE_OPT_NONEG | PARSE_OPT_LITERAL_ARGHELP,
(parse_opt_cb *) cacheinfo_callback}, NULL, 0,
cacheinfo_callback},
{OPTION_CALLBACK, 0, "chmod", &set_executable_bit, "(+|-)x", {OPTION_CALLBACK, 0, "chmod", &set_executable_bit, "(+|-)x",
N_("override the executable bit of the listed files"), N_("override the executable bit of the listed files"),
PARSE_OPT_NONEG, PARSE_OPT_NONEG,
@ -1012,19 +1023,19 @@ int cmd_update_index(int argc, const char **argv, const char *prefix)
{OPTION_LOWLEVEL_CALLBACK, 0, "stdin", &read_from_stdin, NULL, {OPTION_LOWLEVEL_CALLBACK, 0, "stdin", &read_from_stdin, NULL,
N_("read list of paths to be updated from standard input"), N_("read list of paths to be updated from standard input"),
PARSE_OPT_NONEG | PARSE_OPT_NOARG, PARSE_OPT_NONEG | PARSE_OPT_NOARG,
(parse_opt_cb *) stdin_callback}, NULL, 0, stdin_callback},
{OPTION_LOWLEVEL_CALLBACK, 0, "index-info", &nul_term_line, NULL, {OPTION_LOWLEVEL_CALLBACK, 0, "index-info", &nul_term_line, NULL,
N_("add entries from standard input to the index"), N_("add entries from standard input to the index"),
PARSE_OPT_NONEG | PARSE_OPT_NOARG, PARSE_OPT_NONEG | PARSE_OPT_NOARG,
(parse_opt_cb *) stdin_cacheinfo_callback}, NULL, 0, stdin_cacheinfo_callback},
{OPTION_LOWLEVEL_CALLBACK, 0, "unresolve", &has_errors, NULL, {OPTION_LOWLEVEL_CALLBACK, 0, "unresolve", &has_errors, NULL,
N_("repopulate stages #2 and #3 for the listed paths"), N_("repopulate stages #2 and #3 for the listed paths"),
PARSE_OPT_NONEG | PARSE_OPT_NOARG, PARSE_OPT_NONEG | PARSE_OPT_NOARG,
(parse_opt_cb *) unresolve_callback}, NULL, 0, unresolve_callback},
{OPTION_LOWLEVEL_CALLBACK, 'g', "again", &has_errors, NULL, {OPTION_LOWLEVEL_CALLBACK, 'g', "again", &has_errors, NULL,
N_("only update entries that differ from HEAD"), N_("only update entries that differ from HEAD"),
PARSE_OPT_NONEG | PARSE_OPT_NOARG, PARSE_OPT_NONEG | PARSE_OPT_NOARG,
(parse_opt_cb *) reupdate_callback}, NULL, 0, reupdate_callback},
OPT_BIT(0, "ignore-missing", &refresh_args.flags, OPT_BIT(0, "ignore-missing", &refresh_args.flags,
N_("ignore files missing from worktree"), N_("ignore files missing from worktree"),
REFRESH_IGNORE_MISSING), REFRESH_IGNORE_MISSING),

71
diff.c
View File

@ -23,6 +23,7 @@
#include "argv-array.h" #include "argv-array.h"
#include "graph.h" #include "graph.h"
#include "packfile.h" #include "packfile.h"
#include "parse-options.h"
#include "help.h" #include "help.h"
#ifdef NO_FAST_WORKING_DIRECTORY #ifdef NO_FAST_WORKING_DIRECTORY
@ -4491,6 +4492,8 @@ static void run_checkdiff(struct diff_filepair *p, struct diff_options *o)
builtin_checkdiff(name, other, attr_path, p->one, p->two, o); builtin_checkdiff(name, other, attr_path, p->one, p->two, o);
} }
static void prep_parse_options(struct diff_options *options);
void repo_diff_setup(struct repository *r, struct diff_options *options) void repo_diff_setup(struct repository *r, struct diff_options *options)
{ {
memcpy(options, &default_diff_options, sizeof(*options)); memcpy(options, &default_diff_options, sizeof(*options));
@ -4532,6 +4535,8 @@ void repo_diff_setup(struct repository *r, struct diff_options *options)
options->color_moved = diff_color_moved_default; options->color_moved = diff_color_moved_default;
options->color_moved_ws_handling = diff_color_moved_ws_default; options->color_moved_ws_handling = diff_color_moved_ws_default;
prep_parse_options(options);
} }
void diff_setup_done(struct diff_options *options) void diff_setup_done(struct diff_options *options)
@ -4635,6 +4640,8 @@ void diff_setup_done(struct diff_options *options)
if (!options->use_color || external_diff()) if (!options->use_color || external_diff())
options->color_moved = 0; options->color_moved = 0;
FREE_AND_NULL(options->parseopts);
} }
static int opt_arg(const char *arg, int arg_short, const char *arg_long, int *val) static int opt_arg(const char *arg, int arg_short, const char *arg_long, int *val)
@ -4926,6 +4933,47 @@ static int parse_objfind_opt(struct diff_options *opt, const char *arg)
return 1; return 1;
} }
static int diff_opt_unified(const struct option *opt,
const char *arg, int unset)
{
struct diff_options *options = opt->value;
char *s;
BUG_ON_OPT_NEG(unset);
options->context = strtol(arg, &s, 10);
if (*s)
return error(_("%s expects a numerical value"), "--unified");
enable_patch_output(&options->output_format);
return 0;
}
static void prep_parse_options(struct diff_options *options)
{
struct option parseopts[] = {
OPT_GROUP(N_("Diff output format options")),
OPT_BITOP('p', "patch", &options->output_format,
N_("generate patch"),
DIFF_FORMAT_PATCH, DIFF_FORMAT_NO_OUTPUT),
OPT_BITOP('u', NULL, &options->output_format,
N_("generate patch"),
DIFF_FORMAT_PATCH, DIFF_FORMAT_NO_OUTPUT),
OPT_CALLBACK_F('U', "unified", options, N_("<n>"),
N_("generate diffs with <n> lines context"),
PARSE_OPT_NONEG, diff_opt_unified),
OPT_BOOL('W', "function-context", &options->flags.funccontext,
N_("generate diffs with <n> lines context")),
OPT_BIT_F(0, "raw", &options->output_format,
N_("generate the diff in raw format"),
DIFF_FORMAT_RAW, PARSE_OPT_NONEG),
OPT_END()
};
ALLOC_ARRAY(options->parseopts, ARRAY_SIZE(parseopts));
memcpy(options->parseopts, parseopts, sizeof(parseopts));
}
int diff_opt_parse(struct diff_options *options, int diff_opt_parse(struct diff_options *options,
const char **av, int ac, const char *prefix) const char **av, int ac, const char *prefix)
{ {
@ -4936,13 +4984,18 @@ int diff_opt_parse(struct diff_options *options,
if (!prefix) if (!prefix)
prefix = ""; prefix = "";
ac = parse_options(ac, av, prefix, options->parseopts, NULL,
PARSE_OPT_KEEP_DASHDASH |
PARSE_OPT_KEEP_UNKNOWN |
PARSE_OPT_NO_INTERNAL_HELP |
PARSE_OPT_ONE_SHOT |
PARSE_OPT_STOP_AT_NON_OPTION);
if (ac)
return ac;
/* Output format options */ /* Output format options */
if (!strcmp(arg, "-p") || !strcmp(arg, "-u") || !strcmp(arg, "--patch") if (!strcmp(arg, "--patch-with-raw")) {
|| opt_arg(arg, 'U', "unified", &options->context))
enable_patch_output(&options->output_format);
else if (!strcmp(arg, "--raw"))
options->output_format |= DIFF_FORMAT_RAW;
else if (!strcmp(arg, "--patch-with-raw")) {
enable_patch_output(&options->output_format); enable_patch_output(&options->output_format);
options->output_format |= DIFF_FORMAT_RAW; options->output_format |= DIFF_FORMAT_RAW;
} else if (!strcmp(arg, "--numstat")) } else if (!strcmp(arg, "--numstat"))
@ -5230,12 +5283,6 @@ int diff_opt_parse(struct diff_options *options,
else if (opt_arg(arg, '\0', "inter-hunk-context", else if (opt_arg(arg, '\0', "inter-hunk-context",
&options->interhunkcontext)) &options->interhunkcontext))
; ;
else if (!strcmp(arg, "-W"))
options->flags.funccontext = 1;
else if (!strcmp(arg, "--function-context"))
options->flags.funccontext = 1;
else if (!strcmp(arg, "--no-function-context"))
options->flags.funccontext = 0;
else if ((argcount = parse_long_opt("output", av, &optarg))) { else if ((argcount = parse_long_opt("output", av, &optarg))) {
char *path = prefix_filename(prefix, optarg); char *path = prefix_filename(prefix, optarg);
options->file = xfopen(path, "w"); options->file = xfopen(path, "w");

80
diff.h
View File

@ -9,16 +9,17 @@
#include "object.h" #include "object.h"
#include "oidset.h" #include "oidset.h"
struct rev_info; struct combine_diff_path;
struct commit;
struct diff_filespec;
struct diff_options; struct diff_options;
struct diff_queue_struct; struct diff_queue_struct;
struct strbuf;
struct diff_filespec;
struct userdiff_driver;
struct oid_array; struct oid_array;
struct commit; struct option;
struct combine_diff_path;
struct repository; struct repository;
struct rev_info;
struct strbuf;
struct userdiff_driver;
typedef int (*pathchange_fn_t)(struct diff_options *options, typedef int (*pathchange_fn_t)(struct diff_options *options,
struct combine_diff_path *path); struct combine_diff_path *path);
@ -64,39 +65,39 @@ typedef struct strbuf *(*diff_prefix_fn_t)(struct diff_options *opt, void *data)
#define DIFF_FLAGS_INIT { 0 } #define DIFF_FLAGS_INIT { 0 }
struct diff_flags { struct diff_flags {
unsigned recursive:1; unsigned recursive;
unsigned tree_in_recursive:1; unsigned tree_in_recursive;
unsigned binary:1; unsigned binary;
unsigned text:1; unsigned text;
unsigned full_index:1; unsigned full_index;
unsigned silent_on_remove:1; unsigned silent_on_remove;
unsigned find_copies_harder:1; unsigned find_copies_harder;
unsigned follow_renames:1; unsigned follow_renames;
unsigned rename_empty:1; unsigned rename_empty;
unsigned has_changes:1; unsigned has_changes;
unsigned quick:1; unsigned quick;
unsigned no_index:1; unsigned no_index;
unsigned allow_external:1; unsigned allow_external;
unsigned exit_with_status:1; unsigned exit_with_status;
unsigned reverse_diff:1; unsigned reverse_diff;
unsigned check_failed:1; unsigned check_failed;
unsigned relative_name:1; unsigned relative_name;
unsigned ignore_submodules:1; unsigned ignore_submodules;
unsigned dirstat_cumulative:1; unsigned dirstat_cumulative;
unsigned dirstat_by_file:1; unsigned dirstat_by_file;
unsigned allow_textconv:1; unsigned allow_textconv;
unsigned textconv_set_via_cmdline:1; unsigned textconv_set_via_cmdline;
unsigned diff_from_contents:1; unsigned diff_from_contents;
unsigned dirty_submodules:1; unsigned dirty_submodules;
unsigned ignore_untracked_in_submodules:1; unsigned ignore_untracked_in_submodules;
unsigned ignore_dirty_submodules:1; unsigned ignore_dirty_submodules;
unsigned override_submodule_config:1; unsigned override_submodule_config;
unsigned dirstat_by_line:1; unsigned dirstat_by_line;
unsigned funccontext:1; unsigned funccontext;
unsigned default_follow_renames:1; unsigned default_follow_renames;
unsigned stat_with_summary:1; unsigned stat_with_summary;
unsigned suppress_diff_headers:1; unsigned suppress_diff_headers;
unsigned dual_color_diffed_diffs:1; unsigned dual_color_diffed_diffs;
}; };
static inline void diff_flags_or(struct diff_flags *a, static inline void diff_flags_or(struct diff_flags *a,
@ -229,6 +230,7 @@ struct diff_options {
unsigned color_moved_ws_handling; unsigned color_moved_ws_handling;
struct repository *repo; struct repository *repo;
struct option *parseopts;
}; };
void diff_emit_submodule_del(struct diff_options *o, const char *line); void diff_emit_submodule_del(struct diff_options *o, const char *line);

View File

@ -170,9 +170,12 @@ int parse_opt_noop_cb(const struct option *opt, const char *arg, int unset)
* "-h" output even if it's not being handled directly by * "-h" output even if it's not being handled directly by
* parse_options(). * parse_options().
*/ */
int parse_opt_unknown_cb(const struct option *opt, const char *arg, int unset) enum parse_opt_result parse_opt_unknown_cb(struct parse_opt_ctx_t *ctx,
const struct option *opt,
const char *arg, int unset)
{ {
return -2; BUG_ON_OPT_ARG(arg);
return PARSE_OPT_UNKNOWN;
} }
/** /**

View File

@ -20,7 +20,8 @@ int optbug(const struct option *opt, const char *reason)
return error("BUG: switch '%c' %s", opt->short_name, reason); return error("BUG: switch '%c' %s", opt->short_name, reason);
} }
static int get_arg(struct parse_opt_ctx_t *p, const struct option *opt, static enum parse_opt_result get_arg(struct parse_opt_ctx_t *p,
const struct option *opt,
int flags, const char **arg) int flags, const char **arg)
{ {
if (p->opt) { if (p->opt) {
@ -44,7 +45,8 @@ static void fix_filename(const char *prefix, const char **file)
*file = prefix_filename(prefix, *file); *file = prefix_filename(prefix, *file);
} }
static int opt_command_mode_error(const struct option *opt, static enum parse_opt_result opt_command_mode_error(
const struct option *opt,
const struct option *all_opts, const struct option *all_opts,
int flags) int flags)
{ {
@ -69,13 +71,13 @@ static int opt_command_mode_error(const struct option *opt,
error(_("%s is incompatible with %s"), error(_("%s is incompatible with %s"),
optname(opt, flags), that_name.buf); optname(opt, flags), that_name.buf);
strbuf_release(&that_name); strbuf_release(&that_name);
return -1; return PARSE_OPT_ERROR;
} }
return error(_("%s : incompatible with something else"), return error(_("%s : incompatible with something else"),
optname(opt, flags)); optname(opt, flags));
} }
static int get_value(struct parse_opt_ctx_t *p, static enum parse_opt_result get_value(struct parse_opt_ctx_t *p,
const struct option *opt, const struct option *opt,
const struct option *all_opts, const struct option *all_opts,
int flags) int flags)
@ -93,7 +95,7 @@ static int get_value(struct parse_opt_ctx_t *p,
switch (opt->type) { switch (opt->type) {
case OPTION_LOWLEVEL_CALLBACK: case OPTION_LOWLEVEL_CALLBACK:
return (*(parse_opt_ll_cb *)opt->callback)(p, opt, unset); return opt->ll_callback(p, opt, NULL, unset);
case OPTION_BIT: case OPTION_BIT:
if (unset) if (unset)
@ -109,6 +111,13 @@ static int get_value(struct parse_opt_ctx_t *p,
*(int *)opt->value &= ~opt->defval; *(int *)opt->value &= ~opt->defval;
return 0; return 0;
case OPTION_BITOP:
if (unset)
BUG("BITOP can't have unset form");
*(int *)opt->value &= ~opt->extra;
*(int *)opt->value |= opt->defval;
return 0;
case OPTION_COUNTUP: case OPTION_COUNTUP:
if (*(int *)opt->value < 0) if (*(int *)opt->value < 0)
*(int *)opt->value = 0; *(int *)opt->value = 0;
@ -152,16 +161,27 @@ static int get_value(struct parse_opt_ctx_t *p,
return err; return err;
case OPTION_CALLBACK: case OPTION_CALLBACK:
if (unset) {
return (*opt->callback)(opt, NULL, 1) ? (-1) : 0; const char *p_arg = NULL;
if (opt->flags & PARSE_OPT_NOARG) int p_unset;
return (*opt->callback)(opt, NULL, 0) ? (-1) : 0;
if (opt->flags & PARSE_OPT_OPTARG && !p->opt)
return (*opt->callback)(opt, NULL, 0) ? (-1) : 0;
if (get_arg(p, opt, flags, &arg))
return -1;
return (*opt->callback)(opt, arg, 0) ? (-1) : 0;
if (unset)
p_unset = 1;
else if (opt->flags & PARSE_OPT_NOARG)
p_unset = 0;
else if (opt->flags & PARSE_OPT_OPTARG && !p->opt)
p_unset = 0;
else if (get_arg(p, opt, flags, &arg))
return -1;
else {
p_unset = 0;
p_arg = arg;
}
if (opt->callback)
return (*opt->callback)(opt, p_arg, p_unset) ? (-1) : 0;
else
return (*opt->ll_callback)(p, opt, p_arg, p_unset);
}
case OPTION_INTEGER: case OPTION_INTEGER:
if (unset) { if (unset) {
*(int *)opt->value = 0; *(int *)opt->value = 0;
@ -201,7 +221,8 @@ 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) static enum parse_opt_result parse_short_opt(struct parse_opt_ctx_t *p,
const struct option *options)
{ {
const struct option *all_opts = options; const struct option *all_opts = options;
const struct option *numopt = NULL; const struct option *numopt = NULL;
@ -228,14 +249,18 @@ static int parse_short_opt(struct parse_opt_ctx_t *p, const struct option *optio
len++; len++;
arg = xmemdupz(p->opt, len); arg = xmemdupz(p->opt, len);
p->opt = p->opt[len] ? p->opt + len : NULL; p->opt = p->opt[len] ? p->opt + len : NULL;
if (numopt->callback)
rc = (*numopt->callback)(numopt, arg, 0) ? (-1) : 0; rc = (*numopt->callback)(numopt, arg, 0) ? (-1) : 0;
else
rc = (*numopt->ll_callback)(p, numopt, arg, 0);
free(arg); free(arg);
return rc; return rc;
} }
return -2; return PARSE_OPT_UNKNOWN;
} }
static int parse_long_opt(struct parse_opt_ctx_t *p, const char *arg, static enum parse_opt_result parse_long_opt(
struct parse_opt_ctx_t *p, const char *arg,
const struct option *options) const struct option *options)
{ {
const struct option *all_opts = options; const struct option *all_opts = options;
@ -262,11 +287,12 @@ again:
if (*rest) if (*rest)
continue; continue;
p->out[p->cpidx++] = arg - 2; p->out[p->cpidx++] = arg - 2;
return 0; return PARSE_OPT_DONE;
} }
if (!rest) { if (!rest) {
/* abbreviated? */ /* abbreviated? */
if (!strncmp(long_name, arg, arg_end - arg)) { if (!(p->flags & PARSE_OPT_KEEP_UNKNOWN) &&
!strncmp(long_name, arg, arg_end - arg)) {
is_abbreviated: is_abbreviated:
if (abbrev_option) { if (abbrev_option) {
/* /*
@ -326,11 +352,11 @@ is_abbreviated:
ambiguous_option->long_name, ambiguous_option->long_name,
(abbrev_flags & OPT_UNSET) ? "no-" : "", (abbrev_flags & OPT_UNSET) ? "no-" : "",
abbrev_option->long_name); abbrev_option->long_name);
return -3; return PARSE_OPT_HELP;
} }
if (abbrev_option) if (abbrev_option)
return get_value(p, abbrev_option, all_opts, abbrev_flags); return get_value(p, abbrev_option, all_opts, abbrev_flags);
return -2; return PARSE_OPT_UNKNOWN;
} }
static int parse_nodash_opt(struct parse_opt_ctx_t *p, const char *arg, static int parse_nodash_opt(struct parse_opt_ctx_t *p, const char *arg,
@ -400,6 +426,19 @@ static void parse_options_check(const struct option *opts)
if ((opts->flags & PARSE_OPT_OPTARG) || if ((opts->flags & PARSE_OPT_OPTARG) ||
!(opts->flags & PARSE_OPT_NOARG)) !(opts->flags & PARSE_OPT_NOARG))
err |= optbug(opts, "should not accept an argument"); err |= optbug(opts, "should not accept an argument");
break;
case OPTION_CALLBACK:
if (!opts->callback && !opts->ll_callback)
BUG("OPTION_CALLBACK needs one callback");
if (opts->callback && opts->ll_callback)
BUG("OPTION_CALLBACK can't have two callbacks");
break;
case OPTION_LOWLEVEL_CALLBACK:
if (!opts->ll_callback)
BUG("OPTION_LOWLEVEL_CALLBACK needs a callback");
if (opts->callback)
BUG("OPTION_LOWLEVEL_CALLBACK needs no high level callback");
break;
default: default:
; /* ok. (usually accepts an argument) */ ; /* ok. (usually accepts an argument) */
} }
@ -416,15 +455,24 @@ void parse_options_start(struct parse_opt_ctx_t *ctx,
const struct option *options, int flags) const struct option *options, int flags)
{ {
memset(ctx, 0, sizeof(*ctx)); memset(ctx, 0, sizeof(*ctx));
ctx->argc = ctx->total = argc - 1; ctx->argc = argc;
ctx->argv = argv + 1; ctx->argv = argv;
if (!(flags & PARSE_OPT_ONE_SHOT)) {
ctx->argc--;
ctx->argv++;
}
ctx->total = ctx->argc;
ctx->out = argv; ctx->out = argv;
ctx->prefix = prefix; ctx->prefix = prefix;
ctx->cpidx = ((flags & PARSE_OPT_KEEP_ARGV0) != 0); ctx->cpidx = ((flags & PARSE_OPT_KEEP_ARGV0) != 0);
ctx->flags = flags; ctx->flags = flags;
if ((flags & PARSE_OPT_KEEP_UNKNOWN) && if ((flags & PARSE_OPT_KEEP_UNKNOWN) &&
(flags & PARSE_OPT_STOP_AT_NON_OPTION)) (flags & PARSE_OPT_STOP_AT_NON_OPTION) &&
!(flags & PARSE_OPT_ONE_SHOT))
BUG("STOP_AT_NON_OPTION and KEEP_UNKNOWN don't go together"); BUG("STOP_AT_NON_OPTION and KEEP_UNKNOWN don't go together");
if ((flags & PARSE_OPT_ONE_SHOT) &&
(flags & PARSE_OPT_KEEP_ARGV0))
BUG("Can't keep argv0 if you don't have it");
parse_options_check(options); parse_options_check(options);
} }
@ -536,6 +584,10 @@ int parse_options_step(struct parse_opt_ctx_t *ctx,
for (; ctx->argc; ctx->argc--, ctx->argv++) { for (; ctx->argc; ctx->argc--, ctx->argv++) {
const char *arg = ctx->argv[0]; const char *arg = ctx->argv[0];
if (ctx->flags & PARSE_OPT_ONE_SHOT &&
ctx->argc != ctx->total)
break;
if (*arg != '-' || !arg[1]) { if (*arg != '-' || !arg[1]) {
if (parse_nodash_opt(ctx, arg, options) == 0) if (parse_nodash_opt(ctx, arg, options) == 0)
continue; continue;
@ -556,22 +608,28 @@ int parse_options_step(struct parse_opt_ctx_t *ctx,
if (arg[1] != '-') { if (arg[1] != '-') {
ctx->opt = arg + 1; ctx->opt = arg + 1;
switch (parse_short_opt(ctx, options)) { switch (parse_short_opt(ctx, options)) {
case -1: case PARSE_OPT_ERROR:
return PARSE_OPT_ERROR; return PARSE_OPT_ERROR;
case -2: case PARSE_OPT_UNKNOWN:
if (ctx->opt) if (ctx->opt)
check_typos(arg + 1, options); check_typos(arg + 1, options);
if (internal_help && *ctx->opt == 'h') if (internal_help && *ctx->opt == 'h')
goto show_usage; goto show_usage;
goto unknown; goto unknown;
case PARSE_OPT_NON_OPTION:
case PARSE_OPT_HELP:
case PARSE_OPT_COMPLETE:
BUG("parse_short_opt() cannot return these");
case PARSE_OPT_DONE:
break;
} }
if (ctx->opt) if (ctx->opt)
check_typos(arg + 1, options); check_typos(arg + 1, options);
while (ctx->opt) { while (ctx->opt) {
switch (parse_short_opt(ctx, options)) { switch (parse_short_opt(ctx, options)) {
case -1: case PARSE_OPT_ERROR:
return PARSE_OPT_ERROR; return PARSE_OPT_ERROR;
case -2: case PARSE_OPT_UNKNOWN:
if (internal_help && *ctx->opt == 'h') if (internal_help && *ctx->opt == 'h')
goto show_usage; goto show_usage;
@ -583,6 +641,12 @@ int parse_options_step(struct parse_opt_ctx_t *ctx,
ctx->argv[0] = xstrdup(ctx->opt - 1); ctx->argv[0] = xstrdup(ctx->opt - 1);
*(char *)ctx->argv[0] = '-'; *(char *)ctx->argv[0] = '-';
goto unknown; goto unknown;
case PARSE_OPT_NON_OPTION:
case PARSE_OPT_COMPLETE:
case PARSE_OPT_HELP:
BUG("parse_short_opt() cannot return these");
case PARSE_OPT_DONE:
break;
} }
} }
continue; continue;
@ -601,15 +665,22 @@ int parse_options_step(struct parse_opt_ctx_t *ctx,
if (internal_help && !strcmp(arg + 2, "help")) if (internal_help && !strcmp(arg + 2, "help"))
goto show_usage; goto show_usage;
switch (parse_long_opt(ctx, arg + 2, options)) { switch (parse_long_opt(ctx, arg + 2, options)) {
case -1: case PARSE_OPT_ERROR:
return PARSE_OPT_ERROR; return PARSE_OPT_ERROR;
case -2: case PARSE_OPT_UNKNOWN:
goto unknown; goto unknown;
case -3: case PARSE_OPT_HELP:
goto show_usage; goto show_usage;
case PARSE_OPT_NON_OPTION:
case PARSE_OPT_COMPLETE:
BUG("parse_long_opt() cannot return these");
case PARSE_OPT_DONE:
break;
} }
continue; continue;
unknown: unknown:
if (ctx->flags & PARSE_OPT_ONE_SHOT)
break;
if (!(ctx->flags & PARSE_OPT_KEEP_UNKNOWN)) if (!(ctx->flags & PARSE_OPT_KEEP_UNKNOWN))
return PARSE_OPT_UNKNOWN; return PARSE_OPT_UNKNOWN;
ctx->out[ctx->cpidx++] = ctx->argv[0]; ctx->out[ctx->cpidx++] = ctx->argv[0];
@ -623,6 +694,9 @@ unknown:
int parse_options_end(struct parse_opt_ctx_t *ctx) int parse_options_end(struct parse_opt_ctx_t *ctx)
{ {
if (ctx->flags & PARSE_OPT_ONE_SHOT)
return ctx->total - ctx->argc;
MOVE_ARRAY(ctx->out + ctx->cpidx, ctx->argv, ctx->argc); MOVE_ARRAY(ctx->out + ctx->cpidx, ctx->argv, ctx->argc);
ctx->out[ctx->cpidx + ctx->argc] = NULL; ctx->out[ctx->cpidx + ctx->argc] = NULL;
return ctx->cpidx + ctx->argc; return ctx->cpidx + ctx->argc;

View File

@ -10,6 +10,7 @@ enum parse_opt_type {
/* options with no arguments */ /* options with no arguments */
OPTION_BIT, OPTION_BIT,
OPTION_NEGBIT, OPTION_NEGBIT,
OPTION_BITOP,
OPTION_COUNTUP, OPTION_COUNTUP,
OPTION_SET_INT, OPTION_SET_INT,
OPTION_CMDMODE, OPTION_CMDMODE,
@ -27,7 +28,8 @@ enum parse_opt_flags {
PARSE_OPT_STOP_AT_NON_OPTION = 2, PARSE_OPT_STOP_AT_NON_OPTION = 2,
PARSE_OPT_KEEP_ARGV0 = 4, PARSE_OPT_KEEP_ARGV0 = 4,
PARSE_OPT_KEEP_UNKNOWN = 8, PARSE_OPT_KEEP_UNKNOWN = 8,
PARSE_OPT_NO_INTERNAL_HELP = 16 PARSE_OPT_NO_INTERNAL_HELP = 16,
PARSE_OPT_ONE_SHOT = 32
}; };
enum parse_opt_option_flags { enum parse_opt_option_flags {
@ -47,8 +49,9 @@ struct option;
typedef int parse_opt_cb(const struct option *, const char *arg, int unset); typedef int parse_opt_cb(const struct option *, const char *arg, int unset);
struct parse_opt_ctx_t; struct parse_opt_ctx_t;
typedef int parse_opt_ll_cb(struct parse_opt_ctx_t *ctx, typedef enum parse_opt_result parse_opt_ll_cb(struct parse_opt_ctx_t *ctx,
const struct option *opt, int unset); const struct option *opt,
const char *arg, int unset);
/* /*
* `type`:: * `type`::
@ -98,13 +101,16 @@ typedef int parse_opt_ll_cb(struct parse_opt_ctx_t *ctx,
* the option takes optional argument. * the option takes optional argument.
* *
* `callback`:: * `callback`::
* pointer to the callback to use for OPTION_CALLBACK or * pointer to the callback to use for OPTION_CALLBACK
* OPTION_LOWLEVEL_CALLBACK.
* *
* `defval`:: * `defval`::
* default value to fill (*->value) with for PARSE_OPT_OPTARG. * default value to fill (*->value) with for PARSE_OPT_OPTARG.
* OPTION_{BIT,SET_INT} store the {mask,integer} to put in the value when met. * OPTION_{BIT,SET_INT} store the {mask,integer} to put in the value when met.
* CALLBACKS can use it like they want. * CALLBACKS can use it like they want.
*
* `ll_callback`::
* pointer to the callback to use for OPTION_LOWLEVEL_CALLBACK
*
*/ */
struct option { struct option {
enum parse_opt_type type; enum parse_opt_type type;
@ -117,6 +123,8 @@ struct option {
int flags; int flags;
parse_opt_cb *callback; parse_opt_cb *callback;
intptr_t defval; intptr_t defval;
parse_opt_ll_cb *ll_callback;
intptr_t extra;
}; };
#define OPT_BIT_F(s, l, v, h, b, f) { OPTION_BIT, (s), (l), (v), NULL, (h), \ #define OPT_BIT_F(s, l, v, h, b, f) { OPTION_BIT, (s), (l), (v), NULL, (h), \
@ -126,12 +134,17 @@ struct option {
#define OPT_SET_INT_F(s, l, v, h, i, f) { OPTION_SET_INT, (s), (l), (v), NULL, \ #define OPT_SET_INT_F(s, l, v, h, i, f) { OPTION_SET_INT, (s), (l), (v), NULL, \
(h), PARSE_OPT_NOARG | (f), NULL, (i) } (h), PARSE_OPT_NOARG | (f), NULL, (i) }
#define OPT_BOOL_F(s, l, v, h, f) OPT_SET_INT_F(s, l, v, h, 1, f) #define OPT_BOOL_F(s, l, v, h, f) OPT_SET_INT_F(s, l, v, h, 1, f)
#define OPT_CALLBACK_F(s, l, v, a, h, f, cb) \
{ OPTION_CALLBACK, (s), (l), (v), (a), (h), (f), (cb) }
#define OPT_END() { OPTION_END } #define OPT_END() { OPTION_END }
#define OPT_ARGUMENT(l, h) { OPTION_ARGUMENT, 0, (l), NULL, NULL, \ #define OPT_ARGUMENT(l, h) { OPTION_ARGUMENT, 0, (l), NULL, NULL, \
(h), PARSE_OPT_NOARG} (h), PARSE_OPT_NOARG}
#define OPT_GROUP(h) { OPTION_GROUP, 0, NULL, NULL, NULL, (h) } #define OPT_GROUP(h) { OPTION_GROUP, 0, NULL, NULL, NULL, (h) }
#define OPT_BIT(s, l, v, h, b) OPT_BIT_F(s, l, v, h, b, 0) #define OPT_BIT(s, l, v, h, b) OPT_BIT_F(s, l, v, h, b, 0)
#define OPT_BITOP(s, l, v, h, set, clear) { OPTION_BITOP, (s), (l), (v), NULL, (h), \
PARSE_OPT_NOARG|PARSE_OPT_NONEG, NULL, \
(set), NULL, (clear) }
#define OPT_NEGBIT(s, l, v, h, b) { OPTION_NEGBIT, (s), (l), (v), NULL, \ #define OPT_NEGBIT(s, l, v, h, b) { OPTION_NEGBIT, (s), (l), (v), NULL, \
(h), PARSE_OPT_NOARG, NULL, (b) } (h), PARSE_OPT_NOARG, NULL, (b) }
#define OPT_COUNTUP(s, l, v, h) OPT_COUNTUP_F(s, l, v, h, 0) #define OPT_COUNTUP(s, l, v, h) OPT_COUNTUP_F(s, l, v, h, 0)
@ -153,8 +166,7 @@ struct option {
#define OPT_EXPIRY_DATE(s, l, v, h) \ #define OPT_EXPIRY_DATE(s, l, v, h) \
{ OPTION_CALLBACK, (s), (l), (v), N_("expiry-date"),(h), 0, \ { OPTION_CALLBACK, (s), (l), (v), N_("expiry-date"),(h), 0, \
parse_opt_expiry_date_cb } parse_opt_expiry_date_cb }
#define OPT_CALLBACK(s, l, v, a, h, f) \ #define OPT_CALLBACK(s, l, v, a, h, f) OPT_CALLBACK_F(s, l, v, a, h, 0, f)
{ OPTION_CALLBACK, (s), (l), (v), (a), (h), 0, (f) }
#define OPT_NUMBER_CALLBACK(v, h, f) \ #define OPT_NUMBER_CALLBACK(v, h, f) \
{ OPTION_NUMBER, 0, NULL, (v), NULL, (h), \ { OPTION_NUMBER, 0, NULL, (v), NULL, (h), \
PARSE_OPT_NOARG | PARSE_OPT_NONEG, (f) } PARSE_OPT_NOARG | PARSE_OPT_NONEG, (f) }
@ -169,23 +181,31 @@ struct option {
N_("no-op (backward compatibility)"), \ N_("no-op (backward compatibility)"), \
PARSE_OPT_HIDDEN | PARSE_OPT_NOARG, parse_opt_noop_cb } PARSE_OPT_HIDDEN | PARSE_OPT_NOARG, parse_opt_noop_cb }
/* parse_options() will filter out the processed options and leave the /*
* non-option arguments in argv[]. usagestr strings should be marked * parse_options() will filter out the processed options and leave the
* for translation with N_(). * non-option arguments in argv[]. argv0 is assumed program name and
* skipped.
*
* usagestr strings should be marked for translation with N_().
*
* Returns the number of arguments left in argv[]. * Returns the number of arguments left in argv[].
*
* In one-shot mode, argv0 is not a program name, argv[] is left
* untouched and parse_options() returns the number of options
* processed.
*/ */
extern int parse_options(int argc, const char **argv, const char *prefix, int parse_options(int argc, const char **argv, const char *prefix,
const struct option *options, const struct option *options,
const char * const usagestr[], int flags); const char * const usagestr[], int flags);
extern NORETURN void usage_with_options(const char * const *usagestr, NORETURN void usage_with_options(const char * const *usagestr,
const struct option *options); const struct option *options);
extern NORETURN void usage_msg_opt(const char *msg, NORETURN void usage_msg_opt(const char *msg,
const char * const *usagestr, const char * const *usagestr,
const struct option *options); const struct option *options);
extern int optbug(const struct option *opt, const char *reason); int optbug(const struct option *opt, const char *reason);
const char *optname(const struct option *opt, int flags); const char *optname(const struct option *opt, int flags);
/* /*
@ -204,12 +224,12 @@ const char *optname(const struct option *opt, int flags);
/*----- incremental advanced APIs -----*/ /*----- incremental advanced APIs -----*/
enum { enum parse_opt_result {
PARSE_OPT_COMPLETE = -2, PARSE_OPT_COMPLETE = -3,
PARSE_OPT_HELP = -1, PARSE_OPT_HELP = -2,
PARSE_OPT_DONE, PARSE_OPT_ERROR = -1, /* must be the same as error() */
PARSE_OPT_DONE = 0, /* fixed so that "return 0" works */
PARSE_OPT_NON_OPTION, PARSE_OPT_NON_OPTION,
PARSE_OPT_ERROR,
PARSE_OPT_UNKNOWN PARSE_OPT_UNKNOWN
}; };
@ -227,31 +247,31 @@ struct parse_opt_ctx_t {
const char *prefix; const char *prefix;
}; };
extern void parse_options_start(struct parse_opt_ctx_t *ctx, void parse_options_start(struct parse_opt_ctx_t *ctx,
int argc, const char **argv, const char *prefix, int argc, const char **argv, const char *prefix,
const struct option *options, int flags); const struct option *options, int flags);
extern int parse_options_step(struct parse_opt_ctx_t *ctx, int parse_options_step(struct parse_opt_ctx_t *ctx,
const struct option *options, const struct option *options,
const char * const usagestr[]); const char * const usagestr[]);
extern int parse_options_end(struct parse_opt_ctx_t *ctx); int parse_options_end(struct parse_opt_ctx_t *ctx);
extern struct option *parse_options_concat(struct option *a, struct option *b); struct option *parse_options_concat(struct option *a, struct option *b);
/*----- some often used options -----*/ /*----- some often used options -----*/
extern int parse_opt_abbrev_cb(const struct option *, const char *, int); int parse_opt_abbrev_cb(const struct option *, const char *, int);
extern int parse_opt_expiry_date_cb(const struct option *, const char *, int); int parse_opt_expiry_date_cb(const struct option *, const char *, int);
extern int parse_opt_color_flag_cb(const struct option *, const char *, int); int parse_opt_color_flag_cb(const struct option *, const char *, int);
extern int parse_opt_verbosity_cb(const struct option *, const char *, int); int parse_opt_verbosity_cb(const struct option *, const char *, int);
extern int parse_opt_object_name(const struct option *, const char *, int); int parse_opt_object_name(const struct option *, const char *, int);
extern int parse_opt_commits(const struct option *, const char *, int); int parse_opt_commits(const struct option *, const char *, int);
extern int parse_opt_tertiary(const struct option *, const char *, int); int parse_opt_tertiary(const struct option *, const char *, int);
extern int parse_opt_string_list(const struct option *, const char *, int); int parse_opt_string_list(const struct option *, const char *, int);
extern int parse_opt_noop_cb(const struct option *, const char *, int); int parse_opt_noop_cb(const struct option *, const char *, int);
extern int parse_opt_unknown_cb(const struct option *, const char *, int); int parse_opt_unknown_cb(struct parse_opt_ctx_t *ctx, const struct option *, const char *, int);
extern int parse_opt_passthru(const struct option *, const char *, int); int parse_opt_passthru(const struct option *, const char *, int);
extern int parse_opt_passthru_argv(const struct option *, const char *, int); int parse_opt_passthru_argv(const struct option *, const char *, int);
#define OPT__VERBOSE(var, h) OPT_COUNTUP('v', "verbose", (var), (h)) #define OPT__VERBOSE(var, h) OPT_COUNTUP('v', "verbose", (var), (h))
#define OPT__QUIET(var, h) OPT_COUNTUP('q', "quiet", (var), (h)) #define OPT__QUIET(var, h) OPT_COUNTUP('q', "quiet", (var), (h))

View File

@ -546,7 +546,7 @@ do
done >actual done >actual
EOF EOF
test_expect_success SYMLINKS 'difftool --dir-diff --symlink without unstaged changes' ' test_expect_success SYMLINKS 'difftool --dir-diff --symlinks without unstaged changes' '
cat >expect <<-EOF && cat >expect <<-EOF &&
file file
$PWD/file $PWD/file
@ -555,7 +555,7 @@ test_expect_success SYMLINKS 'difftool --dir-diff --symlink without unstaged cha
sub/sub sub/sub
$PWD/sub/sub $PWD/sub/sub
EOF EOF
git difftool --dir-diff --symlink \ git difftool --dir-diff --symlinks \
--extcmd "./.git/CHECK_SYMLINKS" branch HEAD && --extcmd "./.git/CHECK_SYMLINKS" branch HEAD &&
test_cmp expect actual test_cmp expect actual
' '