Merge branch 'cc/fast-import-usage' into seen

The usage string of 'git fast-import' has been updated to use the
parse_options() API for displaying help, and its SYNOPSIS in the
documentation has been standardized to match.

* cc/fast-import-usage:
  fast-import: use struct option for usage string
  fast-import: move command state globals into 'struct fast_import_state'
  fast-import: introduce 'struct fast_import_state'
  fast-import: localize 'i' into the 'for' loops using it
  api-parse-options.adoc: document hidden and OPT_*_F option macros
  api-parse-options.adoc: document per-option flags
  parse-options: introduce OPT_HIDDEN_GROUP
seen
Junio C Hamano 2026-07-25 14:33:21 -07:00
commit 732b8279e7
8 changed files with 344 additions and 148 deletions

View File

@ -9,7 +9,7 @@ git-fast-import - Backend for fast Git data importers
SYNOPSIS
--------
[verse]
frontend | 'git fast-import' [<options>]
'git fast-import' [<options>]

DESCRIPTION
-----------

View File

@ -150,8 +150,76 @@ Data Structure

The main data structure is an array of the `option` struct,
say `static struct option builtin_add_options[]`.

Option flags
~~~~~~~~~~~~

Each option can carry flags in the `flags` field of its `option`
struct. These are per-option flags and are distinct from the
`parse_options()` flags described above; they are usually set through
the `OPT_*_F()` macro variants (see below) rather than by hand. They
are the bitwise-or of:

`PARSE_OPT_OPTARG`::
The option's argument is optional, i.e. both `--option` and
`--option=<value>` are accepted.

`PARSE_OPT_NOARG`::
The option takes no argument at all. Using `--option=<value>`
is rejected.

`PARSE_OPT_NONEG`::
Disable the automatically generated negated `--no-option`
form.

`PARSE_OPT_HIDDEN`::
Hide the option: it is omitted from the usage shown by
`git <cmd> -h`, but is still shown by `git <cmd> --help-all`.
The option is parsed as usual either way. This is meant for
deprecated, advanced or otherwise uncommon options.

`PARSE_OPT_LASTARG_DEFAULT`::
Use the default value (`defval`) when the option is used
without an argument, even for an option that normally requires
one. Only the last argument on the command line takes effect.

`PARSE_OPT_NODASH`::
The option is a single character without a leading dash, such
as the `+` used by some commands.

`PARSE_OPT_LITERAL_ARGHELP`::
Use the argument help string (`argh`) verbatim in the usage
output instead of surrounding it with `<>` or `[]`. Useful when
`argh` already contains a hand-formatted description.

`PARSE_OPT_FROM_ALIAS`::
Internal flag, set on options that were expanded from a
configured alias. It should not be set by callers.

`PARSE_OPT_NOCOMPLETE`::
Do not offer this option for completion.

`PARSE_OPT_COMP_ARG`::
The option's argument, rather than the option itself, is what
should be completed.

`PARSE_OPT_CMDMODE`::
The option is one of several mutually exclusive "command mode"
options that share the same variable. Using more than one of
them at once is rejected.

Macros
~~~~~~

There are some macros to easily define options:

Many of the macros below have an `_F` variant (for example `OPT_BOOL_F`,
`OPT_STRING_F`, `OPT_INTEGER_F`, `OPT_SET_INT_F`, `OPT_BIT_F` and
`OPT_CALLBACK_F`) that takes an additional trailing `flags` argument.
That argument is the bitwise-or of the per-option flags described in the
"Option flags" section above; the non-`_F` macros are simply defined
with `flags` set to `0`.

`OPT__ABBREV(&int_var)`::
Add `--abbrev[=<n>]`.

@ -175,10 +243,21 @@ There are some macros to easily define options:
describes the group or an empty string.
Start the description with an upper-case letter.

`OPT_HIDDEN_GROUP(description)`::
Like `OPT_GROUP()`, but the group header carries
`PARSE_OPT_HIDDEN`, so it is only shown by `--help-all` and not
by `-h`. Use it to label a group that contains only hidden
options, which would otherwise show an empty header under `-h`.

`OPT_BOOL(short, long, &int_var, description)`::
Introduce a boolean option. `int_var` is set to one with
`--option` and set to zero with `--no-option`.

`OPT_HIDDEN_BOOL(short, long, &int_var, description)`::
Like `OPT_BOOL()`, but the option carries `PARSE_OPT_HIDDEN`,
so it is hidden from `-h` while still being shown by
`--help-all`.

`OPT_COUNTUP(short, long, &int_var, description)`::
Introduce a count-up option.
Each use of `--option` increments `int_var`, starting from zero

View File

@ -30,6 +30,7 @@
#include "khash.h"
#include "date.h"
#include "gpg-interface.h"
#include "parse-options.h"

#define PACK_ID_BITS 16
#define MAX_PACK_ID ((1<<PACK_ID_BITS)-1)
@ -184,10 +185,6 @@ static int failure;
static FILE *pack_edges;
static unsigned int show_stats = 1;
static unsigned int quiet;
static int global_argc;
static const char **global_argv;
static const char *global_prefix;

static enum sign_mode signed_tag_mode = SIGN_VERBATIM;
static enum sign_mode signed_commit_mode = SIGN_VERBATIM;
static const char *signed_commit_keyid;
@ -261,9 +258,7 @@ static struct recent_command *rc_free;
static unsigned int cmd_save = 100;
static uintmax_t next_mark;
static struct strbuf new_data = STRBUF_INIT;
static int seen_data_command;
static int require_explicit_termination;
static int allow_unsafe_features;

/* Signal handling */
static volatile sig_atomic_t checkpoint_requested;
@ -276,10 +271,31 @@ static kh_oid_map_t *sub_oid_map;
/* Where to write output of cat-blob commands */
static int cat_blob_fd = STDOUT_FILENO;

static void parse_argv(void);
static void parse_get_mark(const char *p);
static void parse_cat_blob(const char *p);
static void parse_ls(const char *p, struct branch *b);
/* Command state */
struct fast_import_state {
int argc;
const char **argv;
const char *prefix;
int seen_data_command;
int allow_unsafe_features;
struct option *option;
};

static void fast_import_state_init(struct fast_import_state *state,
int argc, const char **argv,
const char *prefix, struct option *option)
{
memset(state, 0, sizeof(*state));
state->argc = argc;
state->argv = argv;
state->prefix = prefix;
state->option = option;
}

static void parse_argv(struct fast_import_state *state);
static void parse_get_mark(struct fast_import_state *state, const char *p);
static void parse_cat_blob(struct fast_import_state *state, const char *p);
static void parse_ls(struct fast_import_state *state, const char *p, struct branch *b);

static void for_each_mark(struct mark_set *m, uintmax_t base, each_mark_fn_t callback, void *p)
{
@ -1847,7 +1863,7 @@ done:
}


static int read_next_command(void)
static int read_next_command(struct fast_import_state *state)
{
static int stdin_eof = 0;

@ -1866,10 +1882,10 @@ static int read_next_command(void)
if (stdin_eof)
return EOF;

if (!seen_data_command
if (!state->seen_data_command
&& !starts_with(command_buf.buf, "feature ")
&& !starts_with(command_buf.buf, "option ")) {
parse_argv();
parse_argv(state);
}

rc = rc_free;
@ -1901,22 +1917,22 @@ static void skip_optional_lf(void)
ungetc(term_char, stdin);
}

static void parse_mark(void)
static void parse_mark(struct fast_import_state *state)
{
const char *v;
if (skip_prefix(command_buf.buf, "mark :", &v)) {
next_mark = strtoumax(v, NULL, 10);
read_next_command();
read_next_command(state);
}
else
next_mark = 0;
}

static void parse_original_identifier(void)
static void parse_original_identifier(struct fast_import_state *state)
{
const char *v;
if (skip_prefix(command_buf.buf, "original-oid ", &v))
read_next_command();
read_next_command(state);
}

static int parse_data(struct strbuf *sb, uintmax_t limit, uintmax_t *len_res)
@ -2070,11 +2086,11 @@ static void parse_and_store_blob(
}
}

static void parse_new_blob(void)
static void parse_new_blob(struct fast_import_state *state)
{
read_next_command();
parse_mark();
parse_original_identifier();
read_next_command(state);
parse_mark(state);
parse_original_identifier(state);
parse_and_store_blob(&last_blob, NULL, next_mark);
}

@ -2370,7 +2386,7 @@ static void parse_path_space(struct strbuf *sb, const char *p,
(*endp)++;
}

static void file_change_m(const char *p, struct branch *b)
static void file_change_m(struct fast_import_state *state, const char *p, struct branch *b)
{
static struct strbuf path = STRBUF_INIT;
struct object_entry *oe;
@ -2437,10 +2453,10 @@ static void file_change_m(const char *p, struct branch *b)
if (S_ISDIR(mode))
die(_("directories cannot be specified 'inline': %s"),
command_buf.buf);
while (read_next_command() != EOF) {
while (read_next_command(state) != EOF) {
const char *v;
if (skip_prefix(command_buf.buf, "cat-blob ", &v))
parse_cat_blob(v);
parse_cat_blob(state, v);
else {
parse_and_store_blob(&last_blob, &oid, 0);
break;
@ -2514,7 +2530,7 @@ static void file_change_cr(const char *p, struct branch *b, int rename)
leaf.tree);
}

static void note_change_n(const char *p, struct branch *b, unsigned char *old_fanout)
static void note_change_n(struct fast_import_state *state, const char *p, struct branch *b, unsigned char *old_fanout)
{
struct object_entry *oe;
struct branch *s;
@ -2579,7 +2595,7 @@ static void note_change_n(const char *p, struct branch *b, unsigned char *old_fa
die(_("invalid ref name or SHA1 expression: %s"), p);

if (inline_data) {
read_next_command();
read_next_command(state);
parse_and_store_blob(&last_blob, &oid, 0);
} else if (oe) {
if (oe->type != OBJ_BLOB)
@ -2646,7 +2662,7 @@ static void parse_from_existing(struct branch *b)
}
}

static int parse_objectish(struct branch *b, const char *objectish)
static int parse_objectish(struct fast_import_state *state, struct branch *b, const char *objectish)
{
struct branch *s;
struct object_id oid;
@ -2689,31 +2705,31 @@ static int parse_objectish(struct branch *b, const char *objectish)
b->branch_tree.tree = NULL;
}

read_next_command();
read_next_command(state);
return 1;
}

static int parse_from(struct branch *b)
static int parse_from(struct fast_import_state *state, struct branch *b)
{
const char *from;

if (!skip_prefix(command_buf.buf, "from ", &from))
return 0;

return parse_objectish(b, from);
return parse_objectish(state, b, from);
}

static int parse_objectish_with_prefix(struct branch *b, const char *prefix)
static int parse_objectish_with_prefix(struct fast_import_state *state, struct branch *b, const char *prefix)
{
const char *base;

if (!skip_prefix(command_buf.buf, prefix, &base))
return 0;

return parse_objectish(b, base);
return parse_objectish(state, b, base);
}

static struct hash_list *parse_merge(unsigned int *count)
static struct hash_list *parse_merge(struct fast_import_state *state, unsigned int *count)
{
struct hash_list *list = NULL, **tail = &list, *n;
const char *from;
@ -2747,7 +2763,7 @@ static struct hash_list *parse_merge(unsigned int *count)
tail = &n->next;

(*count)++;
read_next_command();
read_next_command(state);
}
return list;
}
@ -2758,7 +2774,7 @@ struct signature_data {
struct strbuf data; /* The actual signature data */
};

static void parse_one_signature(struct signature_data *sig, const char *v)
static void parse_one_signature(struct fast_import_state *state, struct signature_data *sig, const char *v)
{
char *args = xstrdup(v); /* Will be freed when sig->hash_algo is freed */
char *space = strchr(args, ' ');
@ -2783,15 +2799,15 @@ static void parse_one_signature(struct signature_data *sig, const char *v)
warning(_("'unknown' signature format in gpgsig"));

/* Read signature data */
read_next_command();
read_next_command(state);
parse_data(&sig->data, 0, NULL);
}

static void discard_one_signature(void)
static void discard_one_signature(struct fast_import_state *state)
{
struct strbuf data = STRBUF_INIT;

read_next_command();
read_next_command(state);
parse_data(&data, 0, NULL);
strbuf_release(&data);
}
@ -2829,13 +2845,14 @@ static void store_signature(struct signature_data *stored_sig,
}
}

static void import_one_signature(struct signature_data *sig_sha1,
static void import_one_signature(struct fast_import_state *state,
struct signature_data *sig_sha1,
struct signature_data *sig_sha256,
const char *v)
{
struct signature_data sig = { NULL, NULL, STRBUF_INIT };

parse_one_signature(&sig, v);
parse_one_signature(state, &sig, v);

if (!strcmp(sig.hash_algo, "sha1"))
store_signature(sig_sha1, &sig, "SHA-1");
@ -2949,7 +2966,7 @@ static void handle_signature_if_invalid(struct strbuf *new_data,
strbuf_release(&tmp_buf);
}

static void parse_new_commit(const char *arg)
static void parse_new_commit(struct fast_import_state *state, const char *arg)
{
static struct strbuf msg = STRBUF_INIT;
struct signature_data sig_sha1 = { NULL, NULL, STRBUF_INIT };
@ -2967,16 +2984,16 @@ static void parse_new_commit(const char *arg)
if (!b)
b = new_branch(arg);

read_next_command();
parse_mark();
parse_original_identifier();
read_next_command(state);
parse_mark(state);
parse_original_identifier(state);
if (skip_prefix(command_buf.buf, "author ", &v)) {
author = parse_ident(v);
read_next_command();
read_next_command(state);
}
if (skip_prefix(command_buf.buf, "committer ", &v)) {
committer = parse_ident(v);
read_next_command();
read_next_command(state);
}
if (!committer)
die(_("expected committer but didn't get one"));
@ -2992,7 +3009,7 @@ static void parse_new_commit(const char *arg)
warning(_("stripping a commit signature"));
/* fallthru */
case SIGN_STRIP:
discard_one_signature();
discard_one_signature(state);
break;

/* Second, modes that parse the signature */
@ -3003,24 +3020,24 @@ static void parse_new_commit(const char *arg)
case SIGN_STRIP_IF_INVALID:
case SIGN_SIGN_IF_INVALID:
case SIGN_ABORT_IF_INVALID:
import_one_signature(&sig_sha1, &sig_sha256, v);
import_one_signature(state, &sig_sha1, &sig_sha256, v);
break;

/* Third, BUG */
default:
BUG("invalid signed_commit_mode value %d", signed_commit_mode);
}
read_next_command();
read_next_command(state);
}

if (skip_prefix(command_buf.buf, "encoding ", &v)) {
encoding = xstrdup(v);
read_next_command();
read_next_command(state);
}
parse_data(&msg, 0, NULL);
read_next_command();
parse_from(b);
merge_list = parse_merge(&merge_count);
read_next_command(state);
parse_from(state, b);
merge_list = parse_merge(state, &merge_count);

/* ensure the branch is active/loaded */
if (!b->branch_tree.tree || !max_active_branches) {
@ -3033,7 +3050,7 @@ static void parse_new_commit(const char *arg)
/* file_change* */
while (command_buf.len > 0) {
if (skip_prefix(command_buf.buf, "M ", &v))
file_change_m(v, b);
file_change_m(state, v, b);
else if (skip_prefix(command_buf.buf, "D ", &v))
file_change_d(v, b);
else if (skip_prefix(command_buf.buf, "R ", &v))
@ -3041,18 +3058,18 @@ static void parse_new_commit(const char *arg)
else if (skip_prefix(command_buf.buf, "C ", &v))
file_change_cr(v, b, 0);
else if (skip_prefix(command_buf.buf, "N ", &v))
note_change_n(v, b, &prev_fanout);
note_change_n(state, v, b, &prev_fanout);
else if (!strcmp("deleteall", command_buf.buf))
file_change_deleteall(b);
else if (skip_prefix(command_buf.buf, "ls ", &v))
parse_ls(v, b);
parse_ls(state, v, b);
else if (skip_prefix(command_buf.buf, "cat-blob ", &v))
parse_cat_blob(v);
parse_cat_blob(state, v);
else {
unread_command_buf = 1;
break;
}
if (read_next_command() == EOF)
if (read_next_command(state) == EOF)
break;
}

@ -3190,7 +3207,7 @@ static void handle_tag_signature(struct strbuf *buf, struct strbuf *msg, const c
}
}

static void parse_new_tag(const char *arg)
static void parse_new_tag(struct fast_import_state *state, const char *arg)
{
static struct strbuf msg = STRBUF_INIT;
const char *from;
@ -3209,8 +3226,8 @@ static void parse_new_tag(const char *arg)
else
first_tag = t;
last_tag = t;
read_next_command();
parse_mark();
read_next_command(state);
parse_mark(state);

/* from ... */
if (!skip_prefix(command_buf.buf, "from ", &from))
@ -3238,15 +3255,15 @@ static void parse_new_tag(const char *arg)
type = oe->type;
} else
die(_("invalid ref name or SHA1 expression: %s"), from);
read_next_command();
read_next_command(state);

/* original-oid ... */
parse_original_identifier();
parse_original_identifier(state);

/* tagger ... */
if (skip_prefix(command_buf.buf, "tagger ", &v)) {
tagger = parse_ident(v);
read_next_command();
read_next_command(state);
} else
tagger = NULL;

@ -3277,7 +3294,7 @@ static void parse_new_tag(const char *arg)
t->pack_id = pack_id;
}

static void parse_reset_branch(const char *arg)
static void parse_reset_branch(struct fast_import_state *state, const char *arg)
{
struct branch *b;
const char *tag_name;
@ -3294,8 +3311,8 @@ static void parse_reset_branch(const char *arg)
}
else
b = new_branch(arg);
read_next_command();
parse_from(b);
read_next_command(state);
parse_from(state, b);
if (b->delete && skip_prefix(b->name, "refs/tags/", &tag_name)) {
/*
* Elsewhere, we call dump_branches() before dump_tags(),
@ -3392,7 +3409,7 @@ static void cat_blob(struct object_entry *oe, struct object_id *oid)
free(buf);
}

static void parse_get_mark(const char *p)
static void parse_get_mark(struct fast_import_state *state UNUSED, const char *p)
{
struct object_entry *oe;
char output[GIT_MAX_HEXSZ + 2];
@ -3409,7 +3426,7 @@ static void parse_get_mark(const char *p)
cat_blob_write(output, the_hash_algo->hexsz + 1);
}

static void parse_cat_blob(const char *p)
static void parse_cat_blob(struct fast_import_state *state UNUSED, const char *p)
{
struct object_entry *oe;
struct object_id oid;
@ -3574,7 +3591,7 @@ static void print_ls(int mode, const unsigned char *hash, const char *path)
cat_blob_write(line.buf, line.len);
}

static void parse_ls(const char *p, struct branch *b)
static void parse_ls(struct fast_import_state *state UNUSED, const char *p, struct branch *b)
{
static struct strbuf path = STRBUF_INIT;
struct tree_entry *root = NULL;
@ -3621,13 +3638,13 @@ static void checkpoint(void)
dump_marks();
}

static void parse_checkpoint(void)
static void parse_checkpoint(struct fast_import_state *state UNUSED)
{
checkpoint_requested = 1;
skip_optional_lf();
}

static void parse_progress(void)
static void parse_progress(struct fast_import_state *state UNUSED)
{
fwrite(command_buf.buf, 1, command_buf.len, stdout);
fputc('\n', stdout);
@ -3635,36 +3652,36 @@ static void parse_progress(void)
skip_optional_lf();
}

static void parse_alias(void)
static void parse_alias(struct fast_import_state *state)
{
struct object_entry *e;
struct branch b;

skip_optional_lf();
read_next_command();
read_next_command(state);

/* mark ... */
parse_mark();
parse_mark(state);
if (!next_mark)
die(_("expected 'mark' command, got %s"), command_buf.buf);

/* to ... */
memset(&b, 0, sizeof(b));
if (!parse_objectish_with_prefix(&b, "to "))
if (!parse_objectish_with_prefix(state, &b, "to "))
die(_("expected 'to' command, got %s"), command_buf.buf);
e = find_object(&b.oid);
assert(e);
insert_mark(&marks, next_mark, e);
}

static char* make_fast_import_path(const char *path)
static char* make_fast_import_path(struct fast_import_state *state, const char *path)
{
if (!relative_marks_paths || is_absolute_path(path))
return prefix_filename(global_prefix, path);
return prefix_filename(state->prefix, path);
return repo_git_path(the_repository, "info/fast-import/%s", path);
}

static void option_import_marks(const char *marks,
static void option_import_marks(struct fast_import_state *state, const char *marks,
int from_stream, int ignore_missing)
{
if (import_marks_file) {
@ -3677,7 +3694,7 @@ static void option_import_marks(const char *marks,
}

free(import_marks_file);
import_marks_file = make_fast_import_path(marks);
import_marks_file = make_fast_import_path(state, marks);
import_marks_file_from_stream = from_stream;
import_marks_file_ignore_missing = ignore_missing;
}
@ -3717,13 +3734,13 @@ static void option_active_branches(const char *branches)
max_active_branches = ulong_arg("--active-branches", branches);
}

static void option_export_marks(const char *marks)
static void option_export_marks(struct fast_import_state *state, const char *marks)
{
free(export_marks_file);
export_marks_file = make_fast_import_path(marks);
export_marks_file = make_fast_import_path(state, marks);
}

static void option_cat_blob_fd(const char *fd)
static void option_cat_blob_fd(struct fast_import_state *state UNUSED, const char *fd)
{
unsigned long n = ulong_arg("--cat-blob-fd", fd);
if (n > (unsigned long) INT_MAX)
@ -3731,16 +3748,16 @@ static void option_cat_blob_fd(const char *fd)
cat_blob_fd = (int) n;
}

static void option_export_pack_edges(const char *edges)
static void option_export_pack_edges(struct fast_import_state *state, const char *edges)
{
char *fn = prefix_filename(global_prefix, edges);
char *fn = prefix_filename(state->prefix, edges);
if (pack_edges)
fclose(pack_edges);
pack_edges = xfopen(fn, "a");
free(fn);
}

static void option_rewrite_submodules(const char *arg, struct string_list *list)
static void option_rewrite_submodules(struct fast_import_state *state, const char *arg, struct string_list *list)
{
struct mark_set *ms;
FILE *fp;
@ -3752,7 +3769,7 @@ static void option_rewrite_submodules(const char *arg, struct string_list *list)
f++;
CALLOC_ARRAY(ms, 1);

f = prefix_filename(global_prefix, f);
f = prefix_filename(state->prefix, f);
fp = fopen(f, "r");
if (!fp)
die_errno(_("cannot read '%s'"), f);
@ -3765,7 +3782,7 @@ static void option_rewrite_submodules(const char *arg, struct string_list *list)
free(s);
}

static int parse_one_option(const char *option)
static int parse_one_option(struct fast_import_state *state, const char *option)
{
if (skip_prefix(option, "max-pack-size=", &option)) {
unsigned long v;
@ -3789,7 +3806,7 @@ static int parse_one_option(const char *option)
} else if (skip_prefix(option, "active-branches=", &option)) {
option_active_branches(option);
} else if (skip_prefix(option, "export-pack-edges=", &option)) {
option_export_pack_edges(option);
option_export_pack_edges(state, option);
} else if (skip_prefix(option, "signed-commits=", &option)) {
if (parse_sign_mode(option, &signed_commit_mode, &signed_commit_keyid))
usagef(_("unknown --signed-commits mode '%s'"), option);
@ -3810,34 +3827,34 @@ static int parse_one_option(const char *option)
return 1;
}

static void check_unsafe_feature(const char *feature, int from_stream)
static void check_unsafe_feature(struct fast_import_state *state, const char *feature, int from_stream)
{
if (from_stream && !allow_unsafe_features)
if (from_stream && !state->allow_unsafe_features)
die(_("feature '%s' forbidden in input without --allow-unsafe-features"),
feature);
}

static int parse_one_feature(const char *feature, int from_stream)
static int parse_one_feature(struct fast_import_state *state, const char *feature, int from_stream)
{
const char *arg;

if (skip_prefix(feature, "date-format=", &arg)) {
option_date_format(arg);
} else if (skip_prefix(feature, "import-marks=", &arg)) {
check_unsafe_feature("import-marks", from_stream);
option_import_marks(arg, from_stream, 0);
check_unsafe_feature(state, "import-marks", from_stream);
option_import_marks(state, arg, from_stream, 0);
} else if (skip_prefix(feature, "import-marks-if-exists=", &arg)) {
check_unsafe_feature("import-marks-if-exists", from_stream);
option_import_marks(arg, from_stream, 1);
check_unsafe_feature(state, "import-marks-if-exists", from_stream);
option_import_marks(state, arg, from_stream, 1);
} else if (skip_prefix(feature, "export-marks=", &arg)) {
check_unsafe_feature(feature, from_stream);
option_export_marks(arg);
check_unsafe_feature(state, feature, from_stream);
option_export_marks(state, arg);
} else if (!strcmp(feature, "alias")) {
; /* Don't die - this feature is supported */
} else if (skip_prefix(feature, "rewrite-submodules-to=", &arg)) {
option_rewrite_submodules(arg, &sub_marks_to);
option_rewrite_submodules(state, arg, &sub_marks_to);
} else if (skip_prefix(feature, "rewrite-submodules-from=", &arg)) {
option_rewrite_submodules(arg, &sub_marks_from);
option_rewrite_submodules(state, arg, &sub_marks_from);
} else if (!strcmp(feature, "get-mark")) {
; /* Don't die - this feature is supported */
} else if (!strcmp(feature, "cat-blob")) {
@ -3859,23 +3876,23 @@ static int parse_one_feature(const char *feature, int from_stream)
return 1;
}

static void parse_feature(const char *feature)
static void parse_feature(struct fast_import_state *state, const char *feature)
{
if (seen_data_command)
if (state->seen_data_command)
die(_("got feature command '%s' after data command"), feature);

if (parse_one_feature(feature, 1))
if (parse_one_feature(state, feature, 1))
return;

die(_("this version of fast-import does not support feature %s."), feature);
}

static void parse_option(const char *option)
static void parse_option(struct fast_import_state *state, const char *option)
{
if (seen_data_command)
if (state->seen_data_command)
die(_("got option command '%s' after data command"), option);

if (parse_one_option(option))
if (parse_one_option(state, option))
return;

die(_("this version of fast-import does not support option: %s"), option);
@ -3908,15 +3925,17 @@ static void git_pack_config(void)
repo_config(the_repository, git_default_config, NULL);
}

static const char fast_import_usage[] =
"git fast-import [--date-format=<f>] [--max-pack-size=<n>] [--big-file-threshold=<n>] [--depth=<n>] [--active-branches=<n>] [--export-marks=<marks.file>]";
static const char *const fast_import_usage[] = {
N_("git fast-import [<options>]"),
NULL
};

static void parse_argv(void)
static void parse_argv(struct fast_import_state *state)
{
unsigned int i;

for (i = 1; i < global_argc; i++) {
const char *a = global_argv[i];
for (i = 1; i < state->argc; i++) {
const char *a = state->argv[i];

if (*a != '-' || !strcmp(a, "--"))
break;
@ -3924,23 +3943,23 @@ static void parse_argv(void)
if (!skip_prefix(a, "--", &a))
die(_("unknown option %s"), a);

if (parse_one_option(a))
if (parse_one_option(state, a))
continue;

if (parse_one_feature(a, 0))
if (parse_one_feature(state, a, 0))
continue;

if (skip_prefix(a, "cat-blob-fd=", &a)) {
option_cat_blob_fd(a);
option_cat_blob_fd(state, a);
continue;
}

die(_("unknown option --%s"), a);
}
if (i != global_argc)
usage(fast_import_usage);
if (i != state->argc)
usage_with_options(fast_import_usage, state->option);

seen_data_command = 1;
state->seen_data_command = 1;
if (import_marks_file)
read_marks();
build_mark_map(&sub_marks_from, &sub_marks_to);
@ -3951,9 +3970,80 @@ int cmd_fast_import(int argc,
const char *prefix,
struct repository *repo)
{
unsigned int i;
struct fast_import_state state;

show_usage_if_asked(argc, argv, fast_import_usage);
unsigned long pack_size_limit, big_file_threshold, depth, active_branches;
char *edges, *signed_commits, *signed_tags, *date_format, *import_marks;
char *import_marks_if_exists, *export_marks, *submodules_from, *submodules_to;
int opt_quiet, opt_show_stats, opt_relative_marks, opt_force, opt_done;
int opt_allow_unsafe;
int cat_blob;

/*
* NEEDSWORK: For now this is used only to render
* `-h`/`--help-all` usage messages. The actual parsing is
* done by parse_one_option()/parse_one_feature().
*/
struct option fast_import_options[] = {
OPT_GROUP(N_("Common")),
OPT_STRING_F(0, "date-format", &date_format, N_("fmt"),
N_("format of the commit/tag dates"), PARSE_OPT_NONEG),
OPT_BOOL_F(0, "stats", &opt_show_stats,
N_("display some basic statistics (objects, packfiles and memory)"),
PARSE_OPT_NONEG),
OPT_BOOL_F(0, "quiet", &opt_quiet,
N_("disable the output shown by --stats"), PARSE_OPT_NONEG),
OPT_BOOL_F(0, "force", &opt_force,
N_("force updating modified existing branches"), PARSE_OPT_NONEG),
OPT_BOOL_F(0, "done", &opt_done,
N_("require a terminating 'done' command"), PARSE_OPT_NONEG),
OPT_UNSIGNED(0, "max-pack-size", &pack_size_limit,
N_("maximum size of each output pack file")),
OPT_UNSIGNED(0, "big-file-threshold", &big_file_threshold,
N_("maximum size of a blob that will be deltified")),
OPT_UNSIGNED(0, "depth", &depth,
N_("maximum delta depth")),
OPT_UNSIGNED(0, "active-branches", &active_branches,
N_("maximum number of branches to maintain active")),
OPT_GROUP(N_("Marks")),
OPT_STRING_F(0, "import-marks", &import_marks, N_("file"),
N_("import marks from <file>"), PARSE_OPT_NONEG),
OPT_STRING_F(0, "import-marks-if-exists", &import_marks_if_exists, N_("file"),
N_("import marks from <file> if it exists"), PARSE_OPT_NONEG),
OPT_STRING_F(0, "export-marks", &export_marks, N_("file"),
N_("dump marks to <file>"), PARSE_OPT_NONEG),
OPT_BOOL(0, "relative-marks", &opt_relative_marks,
N_("are --(import|export)-marks= paths relative to '.git/info/fast-import'?")),
OPT_GROUP(N_("Submodule rewrite")),
OPT_STRING_F(0, "rewrite-submodules-from", &submodules_from, N_("name:filename"),
N_("rewrite object IDs for submodule <name> from <filename>"),
PARSE_OPT_NONEG),
OPT_STRING_F(0, "rewrite-submodules-to", &submodules_to, N_("name:filename"),
N_("rewrite object IDs for submodule <name> to <filename>"),
PARSE_OPT_NONEG),
OPT_GROUP(N_("Signing")),
OPT_STRING_F(0, "signed-commits", &signed_commits, N_("mode"),
N_("how to handle signed commits"),
PARSE_OPT_NONEG),
OPT_STRING_F(0, "signed-tags", &signed_tags, N_("mode"),
N_("how to handle signed tags"),
PARSE_OPT_NONEG),
OPT_HIDDEN_GROUP(N_("Advanced")),
OPT_BOOL_F(0, "allow-unsafe-features", &opt_allow_unsafe,
N_("allow unsafe mark commands from the stream"),
PARSE_OPT_HIDDEN | PARSE_OPT_NONEG),
OPT_STRING_F(0, "export-pack-edges", &edges, N_("file"),
N_("dump edge commits to <file>"),
PARSE_OPT_HIDDEN | PARSE_OPT_NONEG),
OPT_INTEGER_F(0, "cat-blob-fd", &cat_blob,
N_("write some responses to <fd> instead of stdout"),
PARSE_OPT_HIDDEN | PARSE_OPT_NONEG),
OPT_END()
};

show_usage_with_options_if_asked(argc, argv, fast_import_usage, fast_import_options);

fast_import_state_init(&state, argc, argv, prefix, fast_import_options);

reset_pack_idx_option(&pack_idx_opts);
git_pack_config();
@ -3973,54 +4063,50 @@ int cmd_fast_import(int argc,
* line to override stream data). But we must do an early parse of any
* command-line options that impact how we interpret the feature lines.
*/
for (i = 1; i < argc; i++) {
for (int i = 1; i < argc; i++) {
const char *arg = argv[i];
if (*arg != '-' || !strcmp(arg, "--"))
break;
if (!strcmp(arg, "--allow-unsafe-features"))
allow_unsafe_features = 1;
state.allow_unsafe_features = 1;
}

global_argc = argc;
global_argv = argv;
global_prefix = prefix;

rc_free = mem_pool_alloc(&fi_mem_pool, cmd_save * sizeof(*rc_free));
for (i = 0; i < (cmd_save - 1); i++)
for (unsigned int i = 0; i < (cmd_save - 1); i++)
rc_free[i].next = &rc_free[i + 1];
rc_free[cmd_save - 1].next = NULL;

start_packfile();
set_die_routine(die_nicely);
set_checkpoint_signal();
while (read_next_command() != EOF) {
while (read_next_command(&state) != EOF) {
const char *v;
if (!strcmp("blob", command_buf.buf))
parse_new_blob();
parse_new_blob(&state);
else if (skip_prefix(command_buf.buf, "commit ", &v))
parse_new_commit(v);
parse_new_commit(&state, v);
else if (skip_prefix(command_buf.buf, "tag ", &v))
parse_new_tag(v);
parse_new_tag(&state, v);
else if (skip_prefix(command_buf.buf, "reset ", &v))
parse_reset_branch(v);
parse_reset_branch(&state, v);
else if (skip_prefix(command_buf.buf, "ls ", &v))
parse_ls(v, NULL);
parse_ls(&state, v, NULL);
else if (skip_prefix(command_buf.buf, "cat-blob ", &v))
parse_cat_blob(v);
parse_cat_blob(&state, v);
else if (skip_prefix(command_buf.buf, "get-mark ", &v))
parse_get_mark(v);
parse_get_mark(&state, v);
else if (!strcmp("checkpoint", command_buf.buf))
parse_checkpoint();
parse_checkpoint(&state);
else if (!strcmp("done", command_buf.buf))
break;
else if (!strcmp("alias", command_buf.buf))
parse_alias();
parse_alias(&state);
else if (starts_with(command_buf.buf, "progress "))
parse_progress();
parse_progress(&state);
else if (skip_prefix(command_buf.buf, "feature ", &v))
parse_feature(v);
parse_feature(&state, v);
else if (skip_prefix(command_buf.buf, "option git ", &v))
parse_option(v);
parse_option(&state, v);
else if (starts_with(command_buf.buf, "option "))
/* ignore non-git options*/;
else
@ -4031,8 +4117,8 @@ int cmd_fast_import(int argc,
}

/* argv hasn't been parsed yet, do so */
if (!seen_data_command)
parse_argv();
if (!state.seen_data_command)
parse_argv(&state);

if (require_explicit_termination && feof(stdin))
die(_("stream ends early"));
@ -4049,9 +4135,9 @@ int cmd_fast_import(int argc,

if (show_stats) {
uintmax_t total_count = 0, duplicate_count = 0;
for (i = 0; i < ARRAY_SIZE(object_count_by_type); i++)
for (size_t i = 0; i < ARRAY_SIZE(object_count_by_type); i++)
total_count += object_count_by_type[i];
for (i = 0; i < ARRAY_SIZE(duplicate_count_by_type); i++)
for (size_t i = 0; i < ARRAY_SIZE(duplicate_count_by_type); i++)
duplicate_count += duplicate_count_by_type[i];

fprintf(stderr, "%s statistics:\n", argv[0]);

View File

@ -1414,6 +1414,8 @@ static enum parse_opt_result usage_with_options_internal(struct parse_opt_ctx_t

if (opts->type == OPTION_SUBCOMMAND)
continue;
if (!full && (opts->flags & PARSE_OPT_HIDDEN))
continue;
if (opts->type == OPTION_GROUP) {
fputc('\n', outfile);
need_newline = 0;
@ -1421,8 +1423,6 @@ static enum parse_opt_result usage_with_options_internal(struct parse_opt_ctx_t
fprintf(outfile, "%s\n", _(opts->help));
continue;
}
if (!full && (opts->flags & PARSE_OPT_HIDDEN))
continue;

if (need_newline) {
fputc('\n', outfile);

View File

@ -237,6 +237,11 @@ struct option {
.type = OPTION_GROUP, \
.help = (h), \
}
#define OPT_HIDDEN_GROUP(h) { \
.type = OPTION_GROUP, \
.help = (h), \
.flags = PARSE_OPT_HIDDEN, \
}
#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) { \
.type = OPTION_BITOP, \

View File

@ -209,6 +209,10 @@ int cmd__parse_options(int argc, const char **argv)
OPT_GROUP("Alias"),
OPT_STRING('A', "alias-source", &string, "string", "get a string"),
OPT_ALIAS('Z', "alias-target", "alias-source"),
OPT_HIDDEN_GROUP("Hidden options"),
OPT_HIDDEN_BOOL(0, "hidden-bool", &boolean, "get a boolean"),
OPT_INTEGER_F('k', "hidden-integer", &integer, "get a integer",
PARSE_OPT_HIDDEN),
OPT_END(),
};
int ret = 0;

View File

@ -7,7 +7,7 @@ test_description='our own option parser'

. ./test-lib.sh

cat >expect <<\EOF
cat >expect-part1 <<\EOF
usage: test-tool parse-options <options>

A helper function for the parse-options API.
@ -41,6 +41,9 @@ String options
--[no-]string2 <str> get another string
--[no-]st <st> get another string (pervert ordering)
-o <str> get another string
EOF

cat >expect-part2 <<\EOF
--longhelp help text of this entry
spans multiple lines
--[no-]list <str> add str to list
@ -67,12 +70,32 @@ Alias

EOF

cat >expect-noop <<\EOF
--[no-]obsolete no-op (backward compatibility)
EOF

cat >expect-hidden <<\EOF
Hidden options
--[no-]hidden-bool get a boolean
-k, --[no-]hidden-integer <n>
get a integer

EOF

test_expect_success 'test help' '
cat expect-part1 expect-part2 >expect &&
test-tool parse-options -h >output 2>output.err &&
test_must_be_empty output.err &&
test_cmp expect output
'

test_expect_success 'test --help-all shows hidden group and options' '
cat expect-part1 expect-noop expect-part2 expect-hidden >expect-help-all &&
test-tool parse-options --help-all >output 2>output.err &&
test_must_be_empty output.err &&
test_cmp expect-help-all output
'

mv expect expect.err

check () {

View File

@ -12,7 +12,6 @@ column
credential
credential-cache
credential-store
fast-import
fetch-pack
fmt-merge-msg
format-patch