Merge branch 'pz/fetch-submodule-errors-config' into seen
The 'git fetch' command can now configure how submodule fetch errors are handled via 'fetch.submoduleErrors' and '--submodule-errors', making them non-fatal. A premature failure during recursive submodule fetches has been fixed by deferring the error until the OID-based retry phase fails. * pz/fetch-submodule-errors-config: fetch: add fetch.submoduleErrors to make submodule fetch errors non-fatal submodule: fix premature failure in recursive submodule fetchseen
commit
aaa2cae172
|
|
@ -10,6 +10,20 @@
|
||||||
reference.
|
reference.
|
||||||
Defaults to `on-demand`, or to the value of `submodule.recurse` if set.
|
Defaults to `on-demand`, or to the value of `submodule.recurse` if set.
|
||||||
|
|
||||||
|
`fetch.submoduleErrors`::
|
||||||
|
Controls how errors from submodule fetches are handled when
|
||||||
|
`--recurse-submodules` is in effect. When set to `fail` (the default),
|
||||||
|
any submodule fetch error causes the overall `git fetch` or `git pull`
|
||||||
|
to exit with a non-zero status. When set to `warn`, submodule fetch
|
||||||
|
errors are reported to standard error but do not affect the exit
|
||||||
|
status of the command. This is useful when working in repositories
|
||||||
|
where some branches reference submodule commits that are not yet
|
||||||
|
available on the submodule remote, but those commits are not needed
|
||||||
|
for the currently checked-out branch.
|
||||||
|
+
|
||||||
|
The value of this option can be overridden by the `--submodule-errors`
|
||||||
|
option of linkgit:git-fetch[1].
|
||||||
|
|
||||||
`fetch.fsckObjects`::
|
`fetch.fsckObjects`::
|
||||||
If it is set to true, git-fetch-pack will check all fetched
|
If it is set to true, git-fetch-pack will check all fetched
|
||||||
objects. See `transfer.fsckObjects` for what's
|
objects. See `transfer.fsckObjects` for what's
|
||||||
|
|
|
||||||
|
|
@ -294,6 +294,14 @@ ifndef::git-pull[]
|
||||||
`--no-recurse-submodules`::
|
`--no-recurse-submodules`::
|
||||||
Disable recursive fetching of submodules (this has the same effect as
|
Disable recursive fetching of submodules (this has the same effect as
|
||||||
using the `--recurse-submodules=no` option).
|
using the `--recurse-submodules=no` option).
|
||||||
|
|
||||||
|
`--submodule-errors=(fail|warn)`::
|
||||||
|
Control how errors from submodule fetches are handled when
|
||||||
|
`--recurse-submodules` is in effect. When set to `fail` (the default),
|
||||||
|
any submodule fetch error causes the overall `git fetch` to exit with a
|
||||||
|
non-zero status. When set to `warn`, submodule fetch errors are reported
|
||||||
|
to standard error but do not affect the exit status of the command. Can
|
||||||
|
also be configured via `fetch.submoduleErrors`. See linkgit:git-config[1].
|
||||||
endif::git-pull[]
|
endif::git-pull[]
|
||||||
|
|
||||||
`--set-upstream`::
|
`--set-upstream`::
|
||||||
|
|
|
||||||
|
|
@ -111,8 +111,30 @@ struct fetch_config {
|
||||||
int recurse_submodules;
|
int recurse_submodules;
|
||||||
int parallel;
|
int parallel;
|
||||||
int submodule_fetch_jobs;
|
int submodule_fetch_jobs;
|
||||||
|
int submodule_errors;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/* really private - use accessors below to parse and format */
|
||||||
|
static const char *submodule_error_name[] = {
|
||||||
|
[SUBMODULE_ERRORS_FAIL] = "fail",
|
||||||
|
[SUBMODULE_ERRORS_WARN] = "warn",
|
||||||
|
};
|
||||||
|
|
||||||
|
static const char *submodule_error(unsigned num)
|
||||||
|
{
|
||||||
|
if (ARRAY_SIZE(submodule_error_name) <= num)
|
||||||
|
BUG("invalid submodule errors mode %u", num);
|
||||||
|
return submodule_error_name[num];
|
||||||
|
}
|
||||||
|
|
||||||
|
static int parse_submodule_error(const char *name)
|
||||||
|
{
|
||||||
|
for (unsigned num = 0; num < ARRAY_SIZE(submodule_error_name); num++)
|
||||||
|
if (!strcmp(submodule_error_name[num], name))
|
||||||
|
return num;
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
static int git_fetch_config(const char *k, const char *v,
|
static int git_fetch_config(const char *k, const char *v,
|
||||||
const struct config_context *ctx, void *cb)
|
const struct config_context *ctx, void *cb)
|
||||||
{
|
{
|
||||||
|
|
@ -153,6 +175,19 @@ static int git_fetch_config(const char *k, const char *v,
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!strcmp(k, "fetch.submoduleerrors")) {
|
||||||
|
int mode;
|
||||||
|
|
||||||
|
if (!v)
|
||||||
|
return config_error_nonbool(k);
|
||||||
|
mode = parse_submodule_error(v);
|
||||||
|
if (mode < 0)
|
||||||
|
die(_("invalid value for '%s': '%s'"),
|
||||||
|
"fetch.submoduleErrors", v);
|
||||||
|
fetch_config->submodule_errors = mode;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
if (!strcmp(k, "fetch.parallel")) {
|
if (!strcmp(k, "fetch.parallel")) {
|
||||||
fetch_config->parallel = git_config_int(k, v, ctx->kvi);
|
fetch_config->parallel = git_config_int(k, v, ctx->kvi);
|
||||||
if (fetch_config->parallel < 0)
|
if (fetch_config->parallel < 0)
|
||||||
|
|
@ -2243,6 +2278,9 @@ static void add_options_to_argv(struct strvec *argv,
|
||||||
strvec_push(argv, "--no-recurse-submodules");
|
strvec_push(argv, "--no-recurse-submodules");
|
||||||
else if (config->recurse_submodules == RECURSE_SUBMODULES_ON_DEMAND)
|
else if (config->recurse_submodules == RECURSE_SUBMODULES_ON_DEMAND)
|
||||||
strvec_push(argv, "--recurse-submodules=on-demand");
|
strvec_push(argv, "--recurse-submodules=on-demand");
|
||||||
|
if (config->submodule_errors != -1)
|
||||||
|
strvec_pushf(argv, "--submodule-errors=%s",
|
||||||
|
submodule_error(config->submodule_errors));
|
||||||
if (tags == TAGS_SET)
|
if (tags == TAGS_SET)
|
||||||
strvec_push(argv, "--tags");
|
strvec_push(argv, "--tags");
|
||||||
else if (tags == TAGS_UNSET)
|
else if (tags == TAGS_UNSET)
|
||||||
|
|
@ -2502,6 +2540,23 @@ static int fetch_one(struct remote *remote, int argc, const char **argv,
|
||||||
return exit_code;
|
return exit_code;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static int option_parse_submodule_errors(const struct option *opt,
|
||||||
|
const char *arg, int unset)
|
||||||
|
{
|
||||||
|
int *v = opt->value;
|
||||||
|
int mode;
|
||||||
|
|
||||||
|
if (unset) {
|
||||||
|
*v = SUBMODULE_ERRORS_FAIL;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
mode = parse_submodule_error(arg);
|
||||||
|
if (mode < 0)
|
||||||
|
die(_("invalid value for '%s': '%s'"), "--submodule-errors", arg);
|
||||||
|
*v = mode;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
int cmd_fetch(int argc,
|
int cmd_fetch(int argc,
|
||||||
const char **argv,
|
const char **argv,
|
||||||
const char *prefix,
|
const char *prefix,
|
||||||
|
|
@ -2516,6 +2571,7 @@ int cmd_fetch(int argc,
|
||||||
.recurse_submodules = RECURSE_SUBMODULES_DEFAULT,
|
.recurse_submodules = RECURSE_SUBMODULES_DEFAULT,
|
||||||
.parallel = 1,
|
.parallel = 1,
|
||||||
.submodule_fetch_jobs = -1,
|
.submodule_fetch_jobs = -1,
|
||||||
|
.submodule_errors = -1, /* unset */
|
||||||
};
|
};
|
||||||
const char *submodule_prefix = "";
|
const char *submodule_prefix = "";
|
||||||
const char *bundle_uri;
|
const char *bundle_uri;
|
||||||
|
|
@ -2530,6 +2586,7 @@ int cmd_fetch(int argc,
|
||||||
int max_jobs = -1;
|
int max_jobs = -1;
|
||||||
int recurse_submodules_cli = RECURSE_SUBMODULES_DEFAULT;
|
int recurse_submodules_cli = RECURSE_SUBMODULES_DEFAULT;
|
||||||
int recurse_submodules_default = RECURSE_SUBMODULES_ON_DEMAND;
|
int recurse_submodules_default = RECURSE_SUBMODULES_ON_DEMAND;
|
||||||
|
int submodule_errors_cli = -1; /* -1: not set on command line */
|
||||||
int fetch_write_commit_graph = -1;
|
int fetch_write_commit_graph = -1;
|
||||||
int stdin_refspecs = 0;
|
int stdin_refspecs = 0;
|
||||||
int negotiate_only = 0;
|
int negotiate_only = 0;
|
||||||
|
|
@ -2566,6 +2623,10 @@ int cmd_fetch(int argc,
|
||||||
OPT_CALLBACK_F(0, "recurse-submodules", &recurse_submodules_cli, N_("on-demand"),
|
OPT_CALLBACK_F(0, "recurse-submodules", &recurse_submodules_cli, N_("on-demand"),
|
||||||
N_("control recursive fetching of submodules"),
|
N_("control recursive fetching of submodules"),
|
||||||
PARSE_OPT_OPTARG, option_fetch_parse_recurse_submodules),
|
PARSE_OPT_OPTARG, option_fetch_parse_recurse_submodules),
|
||||||
|
OPT_CALLBACK_F(0, "submodule-errors", &submodule_errors_cli,
|
||||||
|
N_("(fail|warn)"),
|
||||||
|
N_("control how submodule fetch errors are handled"),
|
||||||
|
0, option_parse_submodule_errors),
|
||||||
OPT_BOOL(0, "dry-run", &dry_run,
|
OPT_BOOL(0, "dry-run", &dry_run,
|
||||||
N_("dry run")),
|
N_("dry run")),
|
||||||
OPT_BOOL(0, "porcelain", &porcelain, N_("machine-readable output")),
|
OPT_BOOL(0, "porcelain", &porcelain, N_("machine-readable output")),
|
||||||
|
|
@ -2657,6 +2718,9 @@ int cmd_fetch(int argc,
|
||||||
if (recurse_submodules_cli != RECURSE_SUBMODULES_DEFAULT)
|
if (recurse_submodules_cli != RECURSE_SUBMODULES_DEFAULT)
|
||||||
config.recurse_submodules = recurse_submodules_cli;
|
config.recurse_submodules = recurse_submodules_cli;
|
||||||
|
|
||||||
|
if (submodule_errors_cli != -1)
|
||||||
|
config.submodule_errors = submodule_errors_cli;
|
||||||
|
|
||||||
if (negotiate_only) {
|
if (negotiate_only) {
|
||||||
switch (recurse_submodules_cli) {
|
switch (recurse_submodules_cli) {
|
||||||
case RECURSE_SUBMODULES_OFF:
|
case RECURSE_SUBMODULES_OFF:
|
||||||
|
|
@ -2860,11 +2924,14 @@ int cmd_fetch(int argc,
|
||||||
if (!result && remote && (config.recurse_submodules != RECURSE_SUBMODULES_OFF)) {
|
if (!result && remote && (config.recurse_submodules != RECURSE_SUBMODULES_OFF)) {
|
||||||
struct strvec options = STRVEC_INIT;
|
struct strvec options = STRVEC_INIT;
|
||||||
int max_children = max_jobs;
|
int max_children = max_jobs;
|
||||||
|
int submodule_errors = config.submodule_errors;
|
||||||
|
|
||||||
if (max_children < 0)
|
if (max_children < 0)
|
||||||
max_children = config.submodule_fetch_jobs;
|
max_children = config.submodule_fetch_jobs;
|
||||||
if (max_children < 0)
|
if (max_children < 0)
|
||||||
max_children = config.parallel;
|
max_children = config.parallel;
|
||||||
|
if (submodule_errors < 0)
|
||||||
|
submodule_errors = SUBMODULE_ERRORS_FAIL;
|
||||||
|
|
||||||
add_options_to_argv(&options, &config);
|
add_options_to_argv(&options, &config);
|
||||||
trace2_region_enter_printf("fetch", "recurse-submodule", the_repository, "%s", submodule_prefix);
|
trace2_region_enter_printf("fetch", "recurse-submodule", the_repository, "%s", submodule_prefix);
|
||||||
|
|
@ -2874,7 +2941,8 @@ int cmd_fetch(int argc,
|
||||||
config.recurse_submodules,
|
config.recurse_submodules,
|
||||||
recurse_submodules_default,
|
recurse_submodules_default,
|
||||||
verbosity < 0,
|
verbosity < 0,
|
||||||
max_children);
|
max_children,
|
||||||
|
submodule_errors);
|
||||||
trace2_region_leave_printf("fetch", "recurse-submodule", the_repository, "%s", submodule_prefix);
|
trace2_region_leave_printf("fetch", "recurse-submodule", the_repository, "%s", submodule_prefix);
|
||||||
strvec_clear(&options);
|
strvec_clear(&options);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
58
submodule.c
58
submodule.c
|
|
@ -1409,6 +1409,7 @@ struct submodule_parallel_fetch {
|
||||||
int oid_fetch_tasks_nr, oid_fetch_tasks_alloc;
|
int oid_fetch_tasks_nr, oid_fetch_tasks_alloc;
|
||||||
|
|
||||||
struct strbuf submodules_with_errors;
|
struct strbuf submodules_with_errors;
|
||||||
|
int submodule_errors;
|
||||||
};
|
};
|
||||||
#define SPF_INIT { \
|
#define SPF_INIT { \
|
||||||
.args = STRVEC_INIT, \
|
.args = STRVEC_INIT, \
|
||||||
|
|
@ -1562,6 +1563,14 @@ static struct fetch_task *fetch_task_create(struct submodule_parallel_fetch *spf
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void record_fetch_error(struct submodule_parallel_fetch *spf,
|
||||||
|
const char *name)
|
||||||
|
{
|
||||||
|
if (spf->submodule_errors == SUBMODULE_ERRORS_FAIL)
|
||||||
|
spf->result = 1;
|
||||||
|
strbuf_addf(&spf->submodules_with_errors, "\t%s\n", name);
|
||||||
|
}
|
||||||
|
|
||||||
static struct fetch_task *
|
static struct fetch_task *
|
||||||
get_fetch_task_from_index(struct submodule_parallel_fetch *spf,
|
get_fetch_task_from_index(struct submodule_parallel_fetch *spf,
|
||||||
struct strbuf *err)
|
struct strbuf *err)
|
||||||
|
|
@ -1599,7 +1608,7 @@ get_fetch_task_from_index(struct submodule_parallel_fetch *spf,
|
||||||
ce->name);
|
ce->name);
|
||||||
if (S_ISGITLINK(ce->ce_mode) &&
|
if (S_ISGITLINK(ce->ce_mode) &&
|
||||||
!is_empty_dir(empty_submodule_path.buf)) {
|
!is_empty_dir(empty_submodule_path.buf)) {
|
||||||
spf->result = 1;
|
record_fetch_error(spf, ce->name);
|
||||||
strbuf_addf(err,
|
strbuf_addf(err,
|
||||||
_("Could not access submodule '%s'\n"),
|
_("Could not access submodule '%s'\n"),
|
||||||
ce->name);
|
ce->name);
|
||||||
|
|
@ -1753,7 +1762,7 @@ static int fetch_start_failure(struct strbuf *err UNUSED,
|
||||||
struct submodule_parallel_fetch *spf = cb;
|
struct submodule_parallel_fetch *spf = cb;
|
||||||
struct fetch_task *task = task_cb;
|
struct fetch_task *task = task_cb;
|
||||||
|
|
||||||
spf->result = 1;
|
record_fetch_error(spf, task->sub->name);
|
||||||
|
|
||||||
fetch_task_free(task);
|
fetch_task_free(task);
|
||||||
return 0;
|
return 0;
|
||||||
|
|
@ -1779,18 +1788,12 @@ static int fetch_finish(int retvalue, struct strbuf *err UNUSED,
|
||||||
if (!task || !task->sub)
|
if (!task || !task->sub)
|
||||||
BUG("callback cookie bogus");
|
BUG("callback cookie bogus");
|
||||||
|
|
||||||
if (retvalue) {
|
if (retvalue && task->commits) {
|
||||||
/*
|
/*
|
||||||
* NEEDSWORK: This indicates that the overall fetch
|
* This is the second pass (OID-based fetch) and it failed.
|
||||||
* failed, even though there may be a subsequent fetch
|
* The commits are genuinely unavailable from the remote.
|
||||||
* by commit hash that might work. It may be a good
|
|
||||||
* idea to not indicate failure in this case, and only
|
|
||||||
* indicate failure if the subsequent fetch fails.
|
|
||||||
*/
|
*/
|
||||||
spf->result = 1;
|
record_fetch_error(spf, task->sub->name);
|
||||||
|
|
||||||
strbuf_addf(&spf->submodules_with_errors, "\t%s\n",
|
|
||||||
task->sub->name);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Is this the second time we process this submodule? */
|
/* Is this the second time we process this submodule? */
|
||||||
|
|
@ -1798,9 +1801,17 @@ static int fetch_finish(int retvalue, struct strbuf *err UNUSED,
|
||||||
goto out;
|
goto out;
|
||||||
|
|
||||||
it = string_list_lookup(&spf->changed_submodule_names, task->sub->name);
|
it = string_list_lookup(&spf->changed_submodule_names, task->sub->name);
|
||||||
if (!it)
|
if (!it) {
|
||||||
/* Could be an unchanged submodule, not contained in the list */
|
/*
|
||||||
|
* This submodule is not in the changed list (e.g. it was
|
||||||
|
* fetched because RECURSE_SUBMODULES_ON fetches all populated
|
||||||
|
* submodules). A phase 1 failure here has no OID-based retry
|
||||||
|
* to fall back on, so it is a genuine error.
|
||||||
|
*/
|
||||||
|
if (retvalue)
|
||||||
|
record_fetch_error(spf, task->sub->name);
|
||||||
goto out;
|
goto out;
|
||||||
|
}
|
||||||
|
|
||||||
cs_data = it->util;
|
cs_data = it->util;
|
||||||
oid_array_filter(&cs_data->new_commits,
|
oid_array_filter(&cs_data->new_commits,
|
||||||
|
|
@ -1809,6 +1820,11 @@ static int fetch_finish(int retvalue, struct strbuf *err UNUSED,
|
||||||
|
|
||||||
/* Are there commits we want, but do not exist? */
|
/* Are there commits we want, but do not exist? */
|
||||||
if (cs_data->new_commits.nr) {
|
if (cs_data->new_commits.nr) {
|
||||||
|
/*
|
||||||
|
* Schedule an OID-based phase 2 fetch to retrieve the missing
|
||||||
|
* commits directly. Defer any error from phase 1: if phase 2
|
||||||
|
* succeeds, the overall operation should still succeed.
|
||||||
|
*/
|
||||||
task->commits = &cs_data->new_commits;
|
task->commits = &cs_data->new_commits;
|
||||||
ALLOC_GROW(spf->oid_fetch_tasks,
|
ALLOC_GROW(spf->oid_fetch_tasks,
|
||||||
spf->oid_fetch_tasks_nr + 1,
|
spf->oid_fetch_tasks_nr + 1,
|
||||||
|
|
@ -1818,6 +1834,16 @@ static int fetch_finish(int retvalue, struct strbuf *err UNUSED,
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* All required commits are already present locally (they were either
|
||||||
|
* fetched by phase 1 or existed beforehand), so there is no phase 2
|
||||||
|
* retry to defer to. If phase 1 failed, the fetch itself went wrong
|
||||||
|
* (e.g. a transport error) and must still be reported, even though
|
||||||
|
* the gitlinked commits are available.
|
||||||
|
*/
|
||||||
|
if (retvalue)
|
||||||
|
record_fetch_error(spf, task->sub->name);
|
||||||
|
|
||||||
out:
|
out:
|
||||||
fetch_task_free(task);
|
fetch_task_free(task);
|
||||||
return 0;
|
return 0;
|
||||||
|
|
@ -1827,7 +1853,8 @@ int fetch_submodules(struct repository *r,
|
||||||
const struct strvec *options,
|
const struct strvec *options,
|
||||||
const char *prefix, int command_line_option,
|
const char *prefix, int command_line_option,
|
||||||
int default_option,
|
int default_option,
|
||||||
int quiet, int max_parallel_jobs)
|
int quiet, int max_parallel_jobs,
|
||||||
|
int submodule_errors)
|
||||||
{
|
{
|
||||||
struct submodule_parallel_fetch spf = SPF_INIT;
|
struct submodule_parallel_fetch spf = SPF_INIT;
|
||||||
const struct run_process_parallel_opts opts = {
|
const struct run_process_parallel_opts opts = {
|
||||||
|
|
@ -1847,6 +1874,7 @@ int fetch_submodules(struct repository *r,
|
||||||
spf.default_option = default_option;
|
spf.default_option = default_option;
|
||||||
spf.quiet = quiet;
|
spf.quiet = quiet;
|
||||||
spf.prefix = prefix;
|
spf.prefix = prefix;
|
||||||
|
spf.submodule_errors = submodule_errors;
|
||||||
|
|
||||||
if (!r->worktree)
|
if (!r->worktree)
|
||||||
goto out;
|
goto out;
|
||||||
|
|
|
||||||
|
|
@ -90,12 +90,17 @@ int should_update_submodules(void);
|
||||||
*/
|
*/
|
||||||
const struct submodule *submodule_from_ce(const struct cache_entry *ce);
|
const struct submodule *submodule_from_ce(const struct cache_entry *ce);
|
||||||
void check_for_new_submodule_commits(struct object_id *oid);
|
void check_for_new_submodule_commits(struct object_id *oid);
|
||||||
|
/* Values for the submodule_errors parameter of fetch_submodules(). */
|
||||||
|
#define SUBMODULE_ERRORS_FAIL 0 /* submodule fetch errors are fatal (default) */
|
||||||
|
#define SUBMODULE_ERRORS_WARN 1 /* submodule fetch errors are non-fatal warnings */
|
||||||
|
|
||||||
int fetch_submodules(struct repository *r,
|
int fetch_submodules(struct repository *r,
|
||||||
const struct strvec *options,
|
const struct strvec *options,
|
||||||
const char *prefix,
|
const char *prefix,
|
||||||
int command_line_option,
|
int command_line_option,
|
||||||
int default_option,
|
int default_option,
|
||||||
int quiet, int max_parallel_jobs);
|
int quiet, int max_parallel_jobs,
|
||||||
|
int submodule_errors);
|
||||||
unsigned is_submodule_modified(const char *path, int ignore_untracked);
|
unsigned is_submodule_modified(const char *path, int ignore_untracked);
|
||||||
int submodule_uses_gitfile(const char *path);
|
int submodule_uses_gitfile(const char *path);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1262,4 +1262,165 @@ test_expect_success "fetch --all with --no-recurse-submodules only fetches super
|
||||||
test_grep ! "Fetching submodule" fetch-log
|
test_grep ! "Fetching submodule" fetch-log
|
||||||
'
|
'
|
||||||
|
|
||||||
|
# Create an isolated environment for submodule fetch error tests.
|
||||||
|
#
|
||||||
|
# Sets up sub_bare (the submodule upstream), super_bare (the superproject
|
||||||
|
# upstream), super_work (a working clone of super_bare with an initialized
|
||||||
|
# submodule), and clone (a clone of super_bare with an initialized submodule
|
||||||
|
# at a reachable commit). The caller can then create an unreachable commit
|
||||||
|
# and push the superproject to put the clone one commit behind a state it
|
||||||
|
# cannot fully fetch.
|
||||||
|
#
|
||||||
|
# Usage: create_err_env <envdir>
|
||||||
|
create_err_env () {
|
||||||
|
local envdir="$1" &&
|
||||||
|
mkdir "$envdir" &&
|
||||||
|
|
||||||
|
git init --bare "$envdir/sub_bare" &&
|
||||||
|
git clone "$envdir/sub_bare" "$envdir/sub_work" &&
|
||||||
|
test_commit -C "$envdir/sub_work" "${envdir}_base" &&
|
||||||
|
git -C "$envdir/sub_work" push &&
|
||||||
|
|
||||||
|
git init --bare "$envdir/super_bare" &&
|
||||||
|
git clone "$envdir/super_bare" "$envdir/super_work" &&
|
||||||
|
git -C "$envdir/super_work" submodule add \
|
||||||
|
"$pwd/$envdir/sub_bare" sub &&
|
||||||
|
git -C "$envdir/super_work" commit -m "add submodule" &&
|
||||||
|
git -C "$envdir/super_work" push &&
|
||||||
|
|
||||||
|
git clone "$envdir/super_bare" "$envdir/clone" &&
|
||||||
|
git -C "$envdir/clone" submodule update --init
|
||||||
|
}
|
||||||
|
|
||||||
|
# Push a commit to <envdir>/super_bare that records a submodule SHA that is
|
||||||
|
# present locally in super_work/sub but NOT pushed to sub_bare, making the
|
||||||
|
# submodule commit unreachable from clone's sub remote.
|
||||||
|
push_unreachable_commit () {
|
||||||
|
local envdir="$1" &&
|
||||||
|
git -C "$envdir/super_work/sub" commit --allow-empty -m "unreachable" &&
|
||||||
|
git -C "$envdir/super_work" add sub &&
|
||||||
|
git -C "$envdir/super_work" commit -m "point sub to unreachable commit" &&
|
||||||
|
git -C "$envdir/super_work" push
|
||||||
|
}
|
||||||
|
|
||||||
|
test_expect_success 'setup for submodule fetch error tests' '
|
||||||
|
git config --global protocol.file.allow always
|
||||||
|
'
|
||||||
|
|
||||||
|
test_expect_success 'fetch --recurse-submodules fails when submodule commit is unreachable (default)' '
|
||||||
|
test_when_finished "rm -fr env_default" &&
|
||||||
|
create_err_env env_default &&
|
||||||
|
push_unreachable_commit env_default &&
|
||||||
|
test_must_fail git -C env_default/clone fetch --recurse-submodules 2>err &&
|
||||||
|
test_grep "Errors during submodule fetch" err
|
||||||
|
'
|
||||||
|
|
||||||
|
test_expect_success 'fetch.submoduleErrors=warn: unreachable submodule commit is non-fatal' '
|
||||||
|
test_when_finished "rm -fr env_warn_cfg" &&
|
||||||
|
create_err_env env_warn_cfg &&
|
||||||
|
push_unreachable_commit env_warn_cfg &&
|
||||||
|
git -C env_warn_cfg/clone -c fetch.submoduleErrors=warn \
|
||||||
|
fetch --recurse-submodules 2>err &&
|
||||||
|
test_grep "Errors during submodule fetch" err
|
||||||
|
'
|
||||||
|
|
||||||
|
test_expect_success '--submodule-errors=warn: unreachable submodule commit is non-fatal' '
|
||||||
|
test_when_finished "rm -fr env_warn_cli" &&
|
||||||
|
create_err_env env_warn_cli &&
|
||||||
|
push_unreachable_commit env_warn_cli &&
|
||||||
|
git -C env_warn_cli/clone fetch --recurse-submodules \
|
||||||
|
--submodule-errors=warn 2>err &&
|
||||||
|
test_grep "Errors during submodule fetch" err
|
||||||
|
'
|
||||||
|
|
||||||
|
test_expect_success '--submodule-errors=fail: unreachable submodule commit is fatal' '
|
||||||
|
test_when_finished "rm -fr env_fail_cli" &&
|
||||||
|
create_err_env env_fail_cli &&
|
||||||
|
push_unreachable_commit env_fail_cli &&
|
||||||
|
test_must_fail git -C env_fail_cli/clone fetch --recurse-submodules \
|
||||||
|
--submodule-errors=fail 2>err &&
|
||||||
|
test_grep "Errors during submodule fetch" err
|
||||||
|
'
|
||||||
|
|
||||||
|
test_expect_success 'fetch.submoduleErrors=warn does not suppress successful fetch' '
|
||||||
|
# A new reachable submodule commit (pushed to sub_bare) should be
|
||||||
|
# fetched without any error summary.
|
||||||
|
test_when_finished "rm -fr env_ok" &&
|
||||||
|
create_err_env env_ok &&
|
||||||
|
test_commit -C env_ok/sub_work reachable_ok &&
|
||||||
|
git -C env_ok/sub_work push &&
|
||||||
|
git -C env_ok/super_work submodule update --remote &&
|
||||||
|
git -C env_ok/super_work add sub &&
|
||||||
|
git -C env_ok/super_work commit -m "point sub to reachable commit" &&
|
||||||
|
git -C env_ok/super_work push &&
|
||||||
|
git -C env_ok/clone -c fetch.submoduleErrors=warn \
|
||||||
|
fetch --recurse-submodules 2>err &&
|
||||||
|
test_grep ! "Errors during submodule fetch" err
|
||||||
|
'
|
||||||
|
|
||||||
|
test_expect_success 'failed submodule fetch is fatal even when its commits are present locally' '
|
||||||
|
# Create the same commit (unreferenced, via commit-tree with fixed
|
||||||
|
# dates) in both super_work/sub and clone/sub, point the gitlink at
|
||||||
|
# it, and break clone/sub'\''s remote. The commit exists in clone/sub
|
||||||
|
# but is unreachable, so the submodule stays in the changed list; the
|
||||||
|
# fetch failure must still be reported even though there is nothing
|
||||||
|
# left to fetch by commit hash.
|
||||||
|
test_when_finished "rm -fr env_phase1" &&
|
||||||
|
create_err_env env_phase1 &&
|
||||||
|
commit=$(GIT_AUTHOR_DATE="1234567890 +0000" \
|
||||||
|
GIT_COMMITTER_DATE="1234567890 +0000" \
|
||||||
|
git -C env_phase1/super_work/sub commit-tree \
|
||||||
|
"HEAD^{tree}" -p HEAD -m present) &&
|
||||||
|
present=$(GIT_AUTHOR_DATE="1234567890 +0000" \
|
||||||
|
GIT_COMMITTER_DATE="1234567890 +0000" \
|
||||||
|
git -C env_phase1/clone/sub commit-tree \
|
||||||
|
"HEAD^{tree}" -p HEAD -m present) &&
|
||||||
|
test "$commit" = "$present" &&
|
||||||
|
git -C env_phase1/super_work/sub checkout "$commit" &&
|
||||||
|
git -C env_phase1/super_work add sub &&
|
||||||
|
git -C env_phase1/super_work commit -m "gitlink to locally-present commit" &&
|
||||||
|
git -C env_phase1/super_work push &&
|
||||||
|
git -C env_phase1/clone/sub remote set-url origin "$pwd/env_phase1/missing" &&
|
||||||
|
test_must_fail git -C env_phase1/clone fetch --recurse-submodules 2>err &&
|
||||||
|
test_grep "Errors during submodule fetch" err
|
||||||
|
'
|
||||||
|
|
||||||
|
test_expect_success '--submodule-errors=warn is honored by fetch --all' '
|
||||||
|
# A second remote forces fetch_multiple(), which hands the submodule
|
||||||
|
# recursion off to per-remote child processes; the option must be
|
||||||
|
# forwarded to them.
|
||||||
|
test_when_finished "rm -fr env_all" &&
|
||||||
|
create_err_env env_all &&
|
||||||
|
push_unreachable_commit env_all &&
|
||||||
|
git -C env_all/clone remote add second "$pwd/env_all/super_bare" &&
|
||||||
|
git -C env_all/clone fetch --all --recurse-submodules \
|
||||||
|
--submodule-errors=warn 2>err &&
|
||||||
|
test_grep "Errors during submodule fetch" err
|
||||||
|
'
|
||||||
|
|
||||||
|
test_expect_success '--submodule-errors=fail overrides warn config for fetch --all' '
|
||||||
|
# The per-remote child processes re-read the repository config, so
|
||||||
|
# the command-line override must be forwarded to them explicitly.
|
||||||
|
test_when_finished "rm -fr env_override" &&
|
||||||
|
create_err_env env_override &&
|
||||||
|
push_unreachable_commit env_override &&
|
||||||
|
git -C env_override/clone remote add second "$pwd/env_override/super_bare" &&
|
||||||
|
git -C env_override/clone config fetch.submoduleErrors warn &&
|
||||||
|
test_must_fail git -C env_override/clone fetch --all --recurse-submodules \
|
||||||
|
--submodule-errors=fail 2>err &&
|
||||||
|
test_grep "Errors during submodule fetch" err
|
||||||
|
'
|
||||||
|
|
||||||
|
test_expect_success 'fetch.submoduleErrors=warn: inaccessible submodule is non-fatal' '
|
||||||
|
test_when_finished "rm -fr env_access" &&
|
||||||
|
create_err_env env_access &&
|
||||||
|
rm env_access/clone/sub/.git &&
|
||||||
|
rm -r env_access/clone/.git/modules/sub &&
|
||||||
|
git -C env_access/clone -c fetch.submoduleErrors=warn \
|
||||||
|
fetch --recurse-submodules 2>err &&
|
||||||
|
test_grep "Could not access submodule" err &&
|
||||||
|
test_must_fail git -C env_access/clone fetch --recurse-submodules 2>err &&
|
||||||
|
test_grep "Could not access submodule" err
|
||||||
|
'
|
||||||
|
|
||||||
test_done
|
test_done
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue