update-index: migrate to parse-options API

--refresh and --really-refresh accept flags (like -q) and modify
an error indicator.  It might make sense to make the error
indicator global, but just pass the flags and a pointer to the error
indicator in a struct instead.

--cacheinfo wants 3 arguments.  Use the OPTION_LOWLEVEL_CALLBACK
extension to grab them and PARSE_OPT_NOARG to disallow the "sticked"
--cacheinfo=foo form.  (The resulting message

	$ git update-index --cacheinfo=foo
	error: option `cacheinfo' takes no value

is unfortunately incorrect.)

--assume-unchanged and --no-assume-unchanged probably should use the
OPT_UYN feature; but use a callback for now so the existing MARK_FLAG
and UNMARK_FLAG values can be used.

--stdin and --index-info are still constrained to be the last argument
(implemented using the OPTION_LOWLEVEL_CALLBACK extension).

--unresolve and --again consume all arguments that come after them
(also using OPTION_LOWLEVEL_CALLBACK).

The order of options matters.  Each path on the command line is
affected only by the options that come before it.  A custom
argument-parsing loop with parse_options_step() brings that about.

In exchange for all the fuss, we get the usual perks: support for
un-sticked options, better usage error messages, more useful -h
output, and argument parsing code that should be easier to tweak
in the future.

Signed-off-by: Jonathan Nieder <jrnieder@gmail.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
This commit is contained in:
Jonathan Nieder 2010-12-01 17:34:06 -06:00 committed by Junio C Hamano
parent f07d6a1ad1
commit 309be813c9

View file

@ -10,6 +10,7 @@
#include "builtin.h"
#include "refs.h"
#include "resolve-undo.h"
#include "parse-options.h"
/*
* Default to not allowing changes to the list of files. The
@ -397,8 +398,10 @@ static void read_index_info(int line_termination)
strbuf_release(&uq);
}
static const char update_index_usage[] =
"git update-index [-q] [--add] [--replace] [--remove] [--unmerged] [--refresh] [--really-refresh] [--cacheinfo] [--chmod=(+|-)x] [--assume-unchanged] [--skip-worktree|--no-skip-worktree] [--info-only] [--force-remove] [--stdin] [--index-info] [--unresolve] [--again | -g] [--ignore-missing] [-z] [--verbose] [--] [<file>...]";
static const char * const update_index_usage[] = {
"git update-index [options] [--] [<file>...]",
NULL
};
static unsigned char head_sha1[20];
static unsigned char merge_head_sha1[20];
@ -578,16 +581,211 @@ static int do_reupdate(int ac, const char **av,
return 0;
}
struct refresh_params {
unsigned int flags;
int *has_errors;
};
static int refresh(struct refresh_params *o, unsigned int flag)
{
setup_work_tree();
*o->has_errors |= refresh_cache(o->flags | flag);
return 0;
}
static int refresh_callback(const struct option *opt,
const char *arg, int unset)
{
return refresh(opt->value, 0);
}
static int really_refresh_callback(const struct option *opt,
const char *arg, int unset)
{
return refresh(opt->value, REFRESH_REALLY);
}
static int chmod_callback(const struct option *opt,
const char *arg, int unset)
{
char *flip = opt->value;
if ((arg[0] != '-' && arg[0] != '+') || arg[1] != 'x' || arg[2])
return error("option 'chmod' expects \"+x\" or \"-x\"");
*flip = arg[0];
return 0;
}
static int resolve_undo_clear_callback(const struct option *opt,
const char *arg, int unset)
{
resolve_undo_clear();
return 0;
}
static int cacheinfo_callback(struct parse_opt_ctx_t *ctx,
const struct option *opt, int unset)
{
unsigned char sha1[20];
unsigned int mode;
if (ctx->argc <= 3)
return error("option 'cacheinfo' expects three arguments");
if (strtoul_ui(*++ctx->argv, 8, &mode) ||
get_sha1_hex(*++ctx->argv, sha1) ||
add_cacheinfo(mode, sha1, *++ctx->argv, 0))
die("git update-index: --cacheinfo cannot add %s", *ctx->argv);
ctx->argc -= 3;
return 0;
}
static int stdin_cacheinfo_callback(struct parse_opt_ctx_t *ctx,
const struct option *opt, int unset)
{
int *line_termination = opt->value;
if (ctx->argc != 1)
return error("option '%s' must be the last argument", opt->long_name);
allow_add = allow_replace = allow_remove = 1;
read_index_info(*line_termination);
return 0;
}
static int stdin_callback(struct parse_opt_ctx_t *ctx,
const struct option *opt, int unset)
{
int *read_from_stdin = opt->value;
if (ctx->argc != 1)
return error("option '%s' must be the last argument", opt->long_name);
*read_from_stdin = 1;
return 0;
}
static int unresolve_callback(struct parse_opt_ctx_t *ctx,
const struct option *opt, int flags)
{
int *has_errors = opt->value;
const char *prefix = startup_info->prefix;
/* consume remaining arguments. */
*has_errors = do_unresolve(ctx->argc, ctx->argv,
prefix, prefix ? strlen(prefix) : 0);
if (*has_errors)
active_cache_changed = 0;
ctx->argv += ctx->argc - 1;
ctx->argc = 1;
return 0;
}
static int reupdate_callback(struct parse_opt_ctx_t *ctx,
const struct option *opt, int flags)
{
int *has_errors = opt->value;
const char *prefix = startup_info->prefix;
/* consume remaining arguments. */
setup_work_tree();
*has_errors = do_reupdate(ctx->argc, ctx->argv,
prefix, prefix ? strlen(prefix) : 0);
if (*has_errors)
active_cache_changed = 0;
ctx->argv += ctx->argc - 1;
ctx->argc = 1;
return 0;
}
int cmd_update_index(int argc, const char **argv, const char *prefix)
{
int i, newfd, entries, has_errors = 0, line_termination = '\n';
int allow_options = 1;
int newfd, entries, has_errors = 0, line_termination = '\n';
int read_from_stdin = 0;
int prefix_length = prefix ? strlen(prefix) : 0;
char set_executable_bit = 0;
unsigned int refresh_flags = 0;
struct refresh_params refresh_args = {0, &has_errors};
int lock_error = 0;
struct lock_file *lock_file;
struct parse_opt_ctx_t ctx;
int parseopt_state = PARSE_OPT_UNKNOWN;
struct option options[] = {
OPT_BIT('q', NULL, &refresh_args.flags,
"continue refresh even when index needs update",
REFRESH_QUIET),
OPT_BIT(0, "ignore-submodules", &refresh_args.flags,
"refresh: ignore submodules",
REFRESH_IGNORE_SUBMODULES),
OPT_SET_INT(0, "add", &allow_add,
"do not ignore new files", 1),
OPT_SET_INT(0, "replace", &allow_replace,
"let files replace directories and vice-versa", 1),
OPT_SET_INT(0, "remove", &allow_remove,
"notice files missing from worktree", 1),
OPT_BIT(0, "unmerged", &refresh_args.flags,
"refresh even if index contains unmerged entries",
REFRESH_UNMERGED),
{OPTION_CALLBACK, 0, "refresh", &refresh_args, NULL,
"refresh stat information",
PARSE_OPT_NOARG | PARSE_OPT_NONEG,
refresh_callback},
{OPTION_CALLBACK, 0, "really-refresh", &refresh_args, NULL,
"like --refresh, but ignore assume-unchanged setting",
PARSE_OPT_NOARG | PARSE_OPT_NONEG,
really_refresh_callback},
{OPTION_LOWLEVEL_CALLBACK, 0, "cacheinfo", NULL,
"<mode> <object> <path>",
"add the specified entry to the index",
PARSE_OPT_NOARG | /* disallow --cacheinfo=<mode> form */
PARSE_OPT_NONEG | PARSE_OPT_LITERAL_ARGHELP,
(parse_opt_cb *) cacheinfo_callback},
{OPTION_CALLBACK, 0, "chmod", &set_executable_bit, "(+/-)x",
"override the executable bit of the listed files",
PARSE_OPT_NONEG | PARSE_OPT_LITERAL_ARGHELP,
chmod_callback},
{OPTION_SET_INT, 0, "assume-unchanged", &mark_valid_only, NULL,
"mark files as \"not changing\"",
PARSE_OPT_NOARG | PARSE_OPT_NONEG, NULL, MARK_FLAG},
{OPTION_SET_INT, 0, "no-assume-unchanged", &mark_valid_only, NULL,
"clear assumed-unchanged bit",
PARSE_OPT_NOARG | PARSE_OPT_NONEG, NULL, UNMARK_FLAG},
{OPTION_SET_INT, 0, "skip-worktree", &mark_skip_worktree_only, NULL,
"mark files as \"index-only\"",
PARSE_OPT_NOARG | PARSE_OPT_NONEG, NULL, MARK_FLAG},
{OPTION_SET_INT, 0, "no-skip-worktree", &mark_skip_worktree_only, NULL,
"clear skip-worktree bit",
PARSE_OPT_NOARG | PARSE_OPT_NONEG, NULL, UNMARK_FLAG},
OPT_SET_INT(0, "info-only", &info_only,
"add to index only; do not add content to object database", 1),
OPT_SET_INT(0, "force-remove", &force_remove,
"remove named paths even if present in worktree", 1),
OPT_SET_INT('z', NULL, &line_termination,
"with --stdin: input lines are terminated by null bytes", '\0'),
{OPTION_LOWLEVEL_CALLBACK, 0, "stdin", &read_from_stdin, NULL,
"read list of paths to be updated from standard input",
PARSE_OPT_NONEG | PARSE_OPT_NOARG,
(parse_opt_cb *) stdin_callback},
{OPTION_LOWLEVEL_CALLBACK, 0, "index-info", &line_termination, NULL,
"add entries from standard input to the index",
PARSE_OPT_NONEG | PARSE_OPT_NOARG,
(parse_opt_cb *) stdin_cacheinfo_callback},
{OPTION_LOWLEVEL_CALLBACK, 0, "unresolve", &has_errors, NULL,
"repopulate stages #2 and #3 for the listed paths",
PARSE_OPT_NONEG | PARSE_OPT_NOARG,
(parse_opt_cb *) unresolve_callback},
{OPTION_LOWLEVEL_CALLBACK, 'g', "again", &has_errors, NULL,
"only update entries that differ from HEAD",
PARSE_OPT_NONEG | PARSE_OPT_NOARG,
(parse_opt_cb *) reupdate_callback},
OPT_BIT(0, "ignore-missing", &refresh_args.flags,
"ignore files missing from worktree",
REFRESH_IGNORE_MISSING),
OPT_SET_INT(0, "verbose", &verbose,
"report actions to standard output", 1),
{OPTION_CALLBACK, 0, "clear-resolve-undo", NULL, NULL,
"(for porcelains) forget saved unresolved conflicts",
PARSE_OPT_NOARG | PARSE_OPT_NONEG,
resolve_undo_clear_callback},
OPT_END()
};
git_config(git_default_config, NULL);
@ -602,151 +800,48 @@ int cmd_update_index(int argc, const char **argv, const char *prefix)
if (entries < 0)
die("cache corrupted");
for (i = 1 ; i < argc; i++) {
const char *path = argv[i];
const char *p;
/*
* Custom copy of parse_options() because we want to handle
* filename arguments as they come.
*/
parse_options_start(&ctx, argc, argv, prefix,
options, PARSE_OPT_STOP_AT_NON_OPTION);
while (ctx.argc) {
if (parseopt_state != PARSE_OPT_DONE)
parseopt_state = parse_options_step(&ctx, options,
update_index_usage);
if (!ctx.argc)
break;
switch (parseopt_state) {
case PARSE_OPT_HELP:
exit(129);
case PARSE_OPT_NON_OPTION:
case PARSE_OPT_DONE:
{
const char *path = ctx.argv[0];
const char *p;
if (allow_options && *path == '-') {
if (!strcmp(path, "--")) {
allow_options = 0;
continue;
}
if (!strcmp(path, "-q")) {
refresh_flags |= REFRESH_QUIET;
continue;
}
if (!strcmp(path, "--ignore-submodules")) {
refresh_flags |= REFRESH_IGNORE_SUBMODULES;
continue;
}
if (!strcmp(path, "--add")) {
allow_add = 1;
continue;
}
if (!strcmp(path, "--replace")) {
allow_replace = 1;
continue;
}
if (!strcmp(path, "--remove")) {
allow_remove = 1;
continue;
}
if (!strcmp(path, "--unmerged")) {
refresh_flags |= REFRESH_UNMERGED;
continue;
}
if (!strcmp(path, "--refresh")) {
setup_work_tree();
has_errors |= refresh_cache(refresh_flags);
continue;
}
if (!strcmp(path, "--really-refresh")) {
setup_work_tree();
has_errors |= refresh_cache(REFRESH_REALLY | refresh_flags);
continue;
}
if (!strcmp(path, "--cacheinfo")) {
unsigned char sha1[20];
unsigned int mode;
if (i+3 >= argc)
die("git update-index: --cacheinfo <mode> <sha1> <path>");
if (strtoul_ui(argv[i+1], 8, &mode) ||
get_sha1_hex(argv[i+2], sha1) ||
add_cacheinfo(mode, sha1, argv[i+3], 0))
die("git update-index: --cacheinfo"
" cannot add %s", argv[i+3]);
i += 3;
continue;
}
if (!strcmp(path, "--chmod=-x") ||
!strcmp(path, "--chmod=+x")) {
if (argc <= i+1)
die("git update-index: %s <path>", path);
set_executable_bit = path[8];
continue;
}
if (!strcmp(path, "--assume-unchanged")) {
mark_valid_only = MARK_FLAG;
continue;
}
if (!strcmp(path, "--no-assume-unchanged")) {
mark_valid_only = UNMARK_FLAG;
continue;
}
if (!strcmp(path, "--no-skip-worktree")) {
mark_skip_worktree_only = UNMARK_FLAG;
continue;
}
if (!strcmp(path, "--skip-worktree")) {
mark_skip_worktree_only = MARK_FLAG;
continue;
}
if (!strcmp(path, "--info-only")) {
info_only = 1;
continue;
}
if (!strcmp(path, "--force-remove")) {
force_remove = 1;
continue;
}
if (!strcmp(path, "-z")) {
line_termination = 0;
continue;
}
if (!strcmp(path, "--stdin")) {
if (i != argc - 1)
die("--stdin must be at the end");
read_from_stdin = 1;
break;
}
if (!strcmp(path, "--index-info")) {
if (i != argc - 1)
die("--index-info must be at the end");
allow_add = allow_replace = allow_remove = 1;
read_index_info(line_termination);
break;
}
if (!strcmp(path, "--unresolve")) {
has_errors = do_unresolve(argc - i, argv + i,
prefix, prefix_length);
if (has_errors)
active_cache_changed = 0;
goto finish;
}
if (!strcmp(path, "--again") || !strcmp(path, "-g")) {
setup_work_tree();
has_errors = do_reupdate(argc - i, argv + i,
prefix, prefix_length);
if (has_errors)
active_cache_changed = 0;
goto finish;
}
if (!strcmp(path, "--ignore-missing")) {
refresh_flags |= REFRESH_IGNORE_MISSING;
continue;
}
if (!strcmp(path, "--verbose")) {
verbose = 1;
continue;
}
if (!strcmp(path, "--clear-resolve-undo")) {
resolve_undo_clear();
continue;
}
if (!strcmp(path, "-h") || !strcmp(path, "--help"))
usage(update_index_usage);
die("unknown option %s", path);
setup_work_tree();
p = prefix_path(prefix, prefix_length, path);
update_one(p, NULL, 0);
if (set_executable_bit)
chmod_path(set_executable_bit, p);
if (p < path || p > path + strlen(path))
free((char *)p);
ctx.argc--;
ctx.argv++;
break;
}
case PARSE_OPT_UNKNOWN:
if (ctx.argv[0][1] == '-')
error("unknown option '%s'", ctx.argv[0] + 2);
else
error("unknown switch '%c'", *ctx.opt);
usage_with_options(update_index_usage, options);
}
setup_work_tree();
p = prefix_path(prefix, prefix_length, path);
update_one(p, NULL, 0);
if (set_executable_bit)
chmod_path(set_executable_bit, p);
if (p < path || p > path + strlen(path))
free((char *)p);
}
argc = parse_options_end(&ctx);
if (read_from_stdin) {
struct strbuf buf = STRBUF_INIT, nbuf = STRBUF_INIT;
@ -770,10 +865,9 @@ int cmd_update_index(int argc, const char **argv, const char *prefix)
strbuf_release(&buf);
}
finish:
if (active_cache_changed) {
if (newfd < 0) {
if (refresh_flags & REFRESH_QUIET)
if (refresh_args.flags & REFRESH_QUIET)
exit(128);
unable_to_lock_index_die(get_index_file(), lock_error);
}