reference.
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`::
If it is set to true, git-fetch-pack will check all fetched
objects. See `transfer.fsckObjects` for what's
`--no-recurse-submodules`::
Disable recursive fetching of submodules (this has the same effect as
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[]
`--set-upstream`::
int recurse_submodules;
int parallel;
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,
const struct config_context *ctx, void *cb)
{
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")) {
fetch_config->parallel = git_config_int(k, v, ctx->kvi);
if (fetch_config->parallel < 0)
strvec_push(argv, "--no-recurse-submodules");
else if (config->recurse_submodules == 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)
strvec_push(argv, "--tags");
else if (tags == TAGS_UNSET)
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,
const char **argv,
const char *prefix,
.recurse_submodules = RECURSE_SUBMODULES_DEFAULT,
.parallel = 1,
.submodule_fetch_jobs = -1,
+ .submodule_errors = -1, /* unset */
};
const char *submodule_prefix = "";
const char *bundle_uri;
int max_jobs = -1;
int recurse_submodules_cli = RECURSE_SUBMODULES_DEFAULT;
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 stdin_refspecs = 0;
int negotiate_only = 0;
OPT_CALLBACK_F(0, "recurse-submodules", &recurse_submodules_cli, N_("on-demand"),
N_("control recursive fetching of 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,
N_("dry run")),
OPT_BOOL(0, "porcelain", &porcelain, N_("machine-readable output")),
if (recurse_submodules_cli != RECURSE_SUBMODULES_DEFAULT)
config.recurse_submodules = recurse_submodules_cli;
+ if (submodule_errors_cli != -1)
+ config.submodule_errors = submodule_errors_cli;
+
if (negotiate_only) {
switch (recurse_submodules_cli) {
case RECURSE_SUBMODULES_OFF:
if (!result && remote && (config.recurse_submodules != RECURSE_SUBMODULES_OFF)) {
struct strvec options = STRVEC_INIT;
int max_children = max_jobs;
+ int submodule_errors = config.submodule_errors;
if (max_children < 0)
max_children = config.submodule_fetch_jobs;
if (max_children < 0)
max_children = config.parallel;
+ if (submodule_errors < 0)
+ submodule_errors = SUBMODULE_ERRORS_FAIL;
add_options_to_argv(&options, &config);
trace2_region_enter_printf("fetch", "recurse-submodule", the_repository, "%s", submodule_prefix);
config.recurse_submodules,
recurse_submodules_default,
verbosity < 0,
- max_children);
+ max_children,
+ submodule_errors);
trace2_region_leave_printf("fetch", "recurse-submodule", the_repository, "%s", submodule_prefix);
strvec_clear(&options);
}
int oid_fetch_tasks_nr, oid_fetch_tasks_alloc;
struct strbuf submodules_with_errors;
+ int submodule_errors;
};
#define SPF_INIT { \
.args = STRVEC_INIT, \
static void record_fetch_error(struct submodule_parallel_fetch *spf,
const char *name)
{
- spf->result = 1;
+ if (spf->submodule_errors == SUBMODULE_ERRORS_FAIL)
+ spf->result = 1;
strbuf_addf(&spf->submodules_with_errors, "\t%s\n", name);
}
const struct strvec *options,
const char *prefix, int command_line_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;
const struct run_process_parallel_opts opts = {
spf.default_option = default_option;
spf.quiet = quiet;
spf.prefix = prefix;
+ spf.submodule_errors = submodule_errors;
if (!r->worktree)
goto out;
*/
const struct submodule *submodule_from_ce(const struct cache_entry *ce);
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,
const struct strvec *options,
const char *prefix,
int command_line_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);
int submodule_uses_gitfile(const char *path);
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
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