fast-import: use early_scan_options() for --allow-unsafe-features

The "feature" lines at the start of the stream are processed before the
command line options are parsed, so cmd_fast_import() scans its
arguments early to find out if `--allow-unsafe-features` was given.

That scan doesn't know which options take their value as a separate
argument, and it stops at the first argument that doesn't start with a
dash. So it disagrees with parse_options(), which accepts values
separated from their option by a space, for a command line like
"--depth 5 --allow-unsafe-features": the scan stops at "5" and never
sees the option, so unsafe "feature" commands from the stream are
refused even though the option was given.

Let's fix this by building the options for the scan from the same
`struct option` array that parse_options() uses, so that both agree on
which options take a value.

Note that the scan still only matches the exact option spelling, while
parse_options() also accepts unambiguous abbreviations, so the two still
disagree for a command line like "--allow-unsafe". This errs on the safe
side, and is now documented as a restriction.

Signed-off-by: Christian Couder <christian.couder@gmail.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
seen
Christian Couder 2026-09-02 18:10:47 +02:00 committed by Junio C Hamano
parent 3d417fc968
commit aaca161e1e
3 changed files with 48 additions and 22 deletions

View File

@ -66,12 +66,10 @@ fast-import stream! This option is enabled automatically for
remote-helpers that use the `import` capability, as they are
already trusted to run their own code.
+
Note that this option has to be spelled in full, and has to appear
before any option whose value is separated from it by a space, for
the unsafe `feature` commands in the stream to be allowed. So
`--allow-unsafe` or `--depth 5 --allow-unsafe-features` still refuse
them, while `--allow-unsafe-features --depth 5` and
`--depth=5 --allow-unsafe-features` allow them.
Note that this option has to be spelled in full for the unsafe
`feature` commands in the stream to be allowed. So while
`--allow-unsafe` is accepted as an unambiguous abbreviation of this
option, it still refuses them.

`--signed-tags=<mode>`::
Specify how to handle signed tags. Behaves in the same way as

View File

@ -4120,12 +4120,29 @@ static int option_parse_quiet(const struct option *opt UNUSED,
return 0;
}

/*
* The only option the early scan below is interested in, as it decides
* whether unsafe "feature" commands from the stream are allowed.
*/
static const char *early_wanted[] = { "allow-unsafe-features", NULL };

static int option_parse_early_allow_unsafe(
const struct early_scan_option *opt UNUSED,
const char *value UNUSED, int pos UNUSED, void *data)
{
struct fast_import_state *state = data;

state->allow_unsafe_features = 1;
return 0;
}

int cmd_fast_import(int argc,
const char **argv,
const char *prefix,
struct repository *repo)
{
struct fast_import_state state;
struct early_scan_option *early;

struct option fast_import_options[] = {
OPT_GROUP(N_("Common")),
@ -4218,23 +4235,20 @@ 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.
*
* NEEDSWORK: This scan only matches the exact "--allow-unsafe-features"
* spelling and stops at the first argument that doesn't start with a
* dash. As parse_options() below also accepts unambiguous abbreviations
* and values separated by a space from their option, the two disagree
* for command lines like "--allow-unsafe" or "--depth 5
* --allow-unsafe-features": parse_options() accepts the option, but
* this scan doesn't see it, so unsafe features from the stream are
* still refused. This errs on the safe side, but should be fixed by
* teaching this scan about the options that take a value.
* NEEDSWORK: This scan only matches the exact
* "--allow-unsafe-features" spelling, while parse_options() below
* also accepts unambiguous abbreviations, so the two disagree for
* a command line like "--allow-unsafe": parse_options() accepts
* the option, but this scan doesn't see it, so unsafe features
* from the stream are still refused. This errs on the safe side.
*/
for (int i = 1; i < argc; i++) {
const char *arg = argv[i];
if (*arg != '-' || !strcmp(arg, "--"))
break;
if (!strcmp(arg, "--allow-unsafe-features"))
state.allow_unsafe_features = 1;
}
early = early_scan_options_from_options(fast_import_options,
early_wanted);
early_scan_options(argc - 1, argv + 1, early,
EARLY_SCAN_STOP_AT_DASHDASH |
EARLY_SCAN_STOP_AT_NON_OPTION,
option_parse_early_allow_unsafe, &state);
free(early);

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

View File

@ -2344,6 +2344,20 @@ test_expect_success 'R: export-marks options can be overridden by commandline op
test_path_is_missing feature-sub
'

test_expect_success 'R: --allow-unsafe-features found after a value' '
echo "feature import-marks-if-exists=nonexistent.marks" >input &&
git fast-import --allow-unsafe-features <input &&
git fast-import --depth=5 --allow-unsafe-features <input &&
git fast-import --depth 5 --allow-unsafe-features <input &&
git fast-import --date-format raw --allow-unsafe-features <input
'

test_expect_success 'R: --allow-unsafe-features has to be spelled in full' '
echo "feature import-marks-if-exists=nonexistent.marks" >input &&
test_must_fail git fast-import --allow-unsafe <input 2>err &&
test_grep "forbidden in input without --allow-unsafe-features" err
'

test_expect_success 'R: catch typo in marks file name' '
test_must_fail git fast-import --import-marks=nonexistent.marks </dev/null &&
echo "feature import-marks=nonexistent.marks" |