Skip to content

Commit 0b97762

Browse files
pauliuszaleckasgitster
authored andcommitted
fetch: add fetch.submoduleErrors to make submodule fetch errors non-fatal
When fetching with --recurse-submodules, a submodule commit that is not yet reachable from any of the submodule's remote refs causes the entire fetch to fail. This is overly strict when the missing commit belongs to an upstream branch that is still being prepared (e.g. an in-progress merge topic): the local branch does not need that commit, so there is no reason to treat its absence as fatal. Add a new config key fetch.submoduleErrors (values: fail/warn) and a corresponding --submodule-errors=(fail|warn) command-line option that control this behaviour. The default remains fail (existing behaviour); setting the value to warn causes submodule fetch failures to be reported on stderr without affecting the overall exit status of git fetch / git pull. Forward the option to child fetches in add_options_to_argv() so that it also takes effect for `git fetch --all` / `--multiple` (where per-remote child processes handle the submodule recursion themselves) and for nested submodule recursion. The resolved value is forwarded whenever it was set explicitly, in either direction: the per-remote children re-read the repository configuration, so a command-line --submodule-errors=fail must be passed down to them to override fetch.submoduleErrors=warn from the configuration. When neither the configuration nor the command line sets a value, nothing is forwarded and the child processes fall back to their own configuration. Helped-by: Jean-Noël Avila <avila.jn@gmail.com> Helped-by: Ramsay Jones <ramsay@ramsayjones.plus.com> Helped-by: Junio C Hamano <gitster@pobox.com> Signed-off-by: Paulius Zaleckas <paulius.zaleckas@gmail.com> Signed-off-by: Junio C Hamano <gitster@pobox.com>
1 parent d1e198f commit 0b97762

6 files changed

Lines changed: 192 additions & 4 deletions

File tree

‎Documentation/config/fetch.adoc‎

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,20 @@
1010
reference.
1111
Defaults to `on-demand`, or to the value of `submodule.recurse` if set.
1212

13+
`fetch.submoduleErrors`::
14+
Controls how errors from submodule fetches are handled when
15+
`--recurse-submodules` is in effect. When set to `fail` (the default),
16+
any submodule fetch error causes the overall `git fetch` or `git pull`
17+
to exit with a non-zero status. When set to `warn`, submodule fetch
18+
errors are reported to standard error but do not affect the exit
19+
status of the command. This is useful when working in repositories
20+
where some branches reference submodule commits that are not yet
21+
available on the submodule remote, but those commits are not needed
22+
for the currently checked-out branch.
23+
+
24+
The value of this option can be overridden by the `--submodule-errors`
25+
option of linkgit:git-fetch[1].
26+
1327
`fetch.fsckObjects`::
1428
If it is set to true, git-fetch-pack will check all fetched
1529
objects. See `transfer.fsckObjects` for what's

‎Documentation/fetch-options.adoc‎

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -294,6 +294,14 @@ ifndef::git-pull[]
294294
`--no-recurse-submodules`::
295295
Disable recursive fetching of submodules (this has the same effect as
296296
using the `--recurse-submodules=no` option).
297+
298+
`--submodule-errors=(fail|warn)`::
299+
Control how errors from submodule fetches are handled when
300+
`--recurse-submodules` is in effect. When set to `fail` (the default),
301+
any submodule fetch error causes the overall `git fetch` to exit with a
302+
non-zero status. When set to `warn`, submodule fetch errors are reported
303+
to standard error but do not affect the exit status of the command. Can
304+
also be configured via `fetch.submoduleErrors`. See linkgit:git-config[1].
297305
endif::git-pull[]
298306

299307
`--set-upstream`::

‎builtin/fetch.c‎

Lines changed: 69 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -110,8 +110,30 @@ struct fetch_config {
110110
int recurse_submodules;
111111
int parallel;
112112
int submodule_fetch_jobs;
113+
int submodule_errors;
113114
};
114115

116+
/* really private - use accessors below to parse and format */
117+
static const char *submodule_error_name[] = {
118+
[SUBMODULE_ERRORS_FAIL] = "fail",
119+
[SUBMODULE_ERRORS_WARN] = "warn",
120+
};
121+
122+
static const char *submodule_error(unsigned num)
123+
{
124+
if (ARRAY_SIZE(submodule_error_name) <= num)
125+
BUG("invalid submodule errors mode %u", num);
126+
return submodule_error_name[num];
127+
}
128+
129+
static int parse_submodule_error(const char *name)
130+
{
131+
for (unsigned num = 0; num < ARRAY_SIZE(submodule_error_name); num++)
132+
if (!strcmp(submodule_error_name[num], name))
133+
return num;
134+
return -1;
135+
}
136+
115137
static int git_fetch_config(const char *k, const char *v,
116138
const struct config_context *ctx, void *cb)
117139
{
@@ -152,6 +174,19 @@ static int git_fetch_config(const char *k, const char *v,
152174
return 0;
153175
}
154176

177+
if (!strcmp(k, "fetch.submoduleerrors")) {
178+
int mode;
179+
180+
if (!v)
181+
return config_error_nonbool(k);
182+
mode = parse_submodule_error(v);
183+
if (mode < 0)
184+
die(_("invalid value for '%s': '%s'"),
185+
"fetch.submoduleErrors", v);
186+
fetch_config->submodule_errors = mode;
187+
return 0;
188+
}
189+
155190
if (!strcmp(k, "fetch.parallel")) {
156191
fetch_config->parallel = git_config_int(k, v, ctx->kvi);
157192
if (fetch_config->parallel < 0)
@@ -2205,6 +2240,9 @@ static void add_options_to_argv(struct strvec *argv,
22052240
strvec_push(argv, "--no-recurse-submodules");
22062241
else if (config->recurse_submodules == RECURSE_SUBMODULES_ON_DEMAND)
22072242
strvec_push(argv, "--recurse-submodules=on-demand");
2243+
if (config->submodule_errors != -1)
2244+
strvec_pushf(argv, "--submodule-errors=%s",
2245+
submodule_error(config->submodule_errors));
22082246
if (tags == TAGS_SET)
22092247
strvec_push(argv, "--tags");
22102248
else if (tags == TAGS_UNSET)
@@ -2464,6 +2502,23 @@ static int fetch_one(struct remote *remote, int argc, const char **argv,
24642502
return exit_code;
24652503
}
24662504

2505+
static int option_parse_submodule_errors(const struct option *opt,
2506+
const char *arg, int unset)
2507+
{
2508+
int *v = opt->value;
2509+
int mode;
2510+
2511+
if (unset) {
2512+
*v = SUBMODULE_ERRORS_FAIL;
2513+
return 0;
2514+
}
2515+
mode = parse_submodule_error(arg);
2516+
if (mode < 0)
2517+
die(_("invalid value for '%s': '%s'"), "--submodule-errors", arg);
2518+
*v = mode;
2519+
return 0;
2520+
}
2521+
24672522
int cmd_fetch(int argc,
24682523
const char **argv,
24692524
const char *prefix,
@@ -2477,6 +2532,7 @@ int cmd_fetch(int argc,
24772532
.recurse_submodules = RECURSE_SUBMODULES_DEFAULT,
24782533
.parallel = 1,
24792534
.submodule_fetch_jobs = -1,
2535+
.submodule_errors = -1, /* unset */
24802536
};
24812537
const char *submodule_prefix = "";
24822538
const char *bundle_uri;
@@ -2491,6 +2547,7 @@ int cmd_fetch(int argc,
24912547
int max_jobs = -1;
24922548
int recurse_submodules_cli = RECURSE_SUBMODULES_DEFAULT;
24932549
int recurse_submodules_default = RECURSE_SUBMODULES_ON_DEMAND;
2550+
int submodule_errors_cli = -1; /* -1: not set on command line */
24942551
int fetch_write_commit_graph = -1;
24952552
int stdin_refspecs = 0;
24962553
int negotiate_only = 0;
@@ -2527,6 +2584,10 @@ int cmd_fetch(int argc,
25272584
OPT_CALLBACK_F(0, "recurse-submodules", &recurse_submodules_cli, N_("on-demand"),
25282585
N_("control recursive fetching of submodules"),
25292586
PARSE_OPT_OPTARG, option_fetch_parse_recurse_submodules),
2587+
OPT_CALLBACK_F(0, "submodule-errors", &submodule_errors_cli,
2588+
N_("(fail|warn)"),
2589+
N_("control how submodule fetch errors are handled"),
2590+
0, option_parse_submodule_errors),
25302591
OPT_BOOL(0, "dry-run", &dry_run,
25312592
N_("dry run")),
25322593
OPT_BOOL(0, "porcelain", &porcelain, N_("machine-readable output")),
@@ -2616,6 +2677,9 @@ int cmd_fetch(int argc,
26162677
if (recurse_submodules_cli != RECURSE_SUBMODULES_DEFAULT)
26172678
config.recurse_submodules = recurse_submodules_cli;
26182679

2680+
if (submodule_errors_cli != -1)
2681+
config.submodule_errors = submodule_errors_cli;
2682+
26192683
if (negotiate_only) {
26202684
switch (recurse_submodules_cli) {
26212685
case RECURSE_SUBMODULES_OFF:
@@ -2819,11 +2883,14 @@ int cmd_fetch(int argc,
28192883
if (!result && remote && (config.recurse_submodules != RECURSE_SUBMODULES_OFF)) {
28202884
struct strvec options = STRVEC_INIT;
28212885
int max_children = max_jobs;
2886+
int submodule_errors = config.submodule_errors;
28222887

28232888
if (max_children < 0)
28242889
max_children = config.submodule_fetch_jobs;
28252890
if (max_children < 0)
28262891
max_children = config.parallel;
2892+
if (submodule_errors < 0)
2893+
submodule_errors = SUBMODULE_ERRORS_FAIL;
28272894

28282895
add_options_to_argv(&options, &config);
28292896
trace2_region_enter_printf("fetch", "recurse-submodule", the_repository, "%s", submodule_prefix);
@@ -2833,7 +2900,8 @@ int cmd_fetch(int argc,
28332900
config.recurse_submodules,
28342901
recurse_submodules_default,
28352902
verbosity < 0,
2836-
max_children);
2903+
max_children,
2904+
submodule_errors);
28372905
trace2_region_leave_printf("fetch", "recurse-submodule", the_repository, "%s", submodule_prefix);
28382906
strvec_clear(&options);
28392907
}

‎submodule.c‎

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1409,6 +1409,7 @@ struct submodule_parallel_fetch {
14091409
int oid_fetch_tasks_nr, oid_fetch_tasks_alloc;
14101410

14111411
struct strbuf submodules_with_errors;
1412+
int submodule_errors;
14121413
};
14131414
#define SPF_INIT { \
14141415
.args = STRVEC_INIT, \
@@ -1565,7 +1566,8 @@ static struct fetch_task *fetch_task_create(struct submodule_parallel_fetch *spf
15651566
static void record_fetch_error(struct submodule_parallel_fetch *spf,
15661567
const char *name)
15671568
{
1568-
spf->result = 1;
1569+
if (spf->submodule_errors == SUBMODULE_ERRORS_FAIL)
1570+
spf->result = 1;
15691571
strbuf_addf(&spf->submodules_with_errors, "\t%s\n", name);
15701572
}
15711573

@@ -1851,7 +1853,8 @@ int fetch_submodules(struct repository *r,
18511853
const struct strvec *options,
18521854
const char *prefix, int command_line_option,
18531855
int default_option,
1854-
int quiet, int max_parallel_jobs)
1856+
int quiet, int max_parallel_jobs,
1857+
int submodule_errors)
18551858
{
18561859
struct submodule_parallel_fetch spf = SPF_INIT;
18571860
const struct run_process_parallel_opts opts = {
@@ -1871,6 +1874,7 @@ int fetch_submodules(struct repository *r,
18711874
spf.default_option = default_option;
18721875
spf.quiet = quiet;
18731876
spf.prefix = prefix;
1877+
spf.submodule_errors = submodule_errors;
18741878

18751879
if (!r->worktree)
18761880
goto out;

‎submodule.h‎

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -90,12 +90,17 @@ int should_update_submodules(void);
9090
*/
9191
const struct submodule *submodule_from_ce(const struct cache_entry *ce);
9292
void check_for_new_submodule_commits(struct object_id *oid);
93+
/* Values for the submodule_errors parameter of fetch_submodules(). */
94+
#define SUBMODULE_ERRORS_FAIL 0 /* submodule fetch errors are fatal (default) */
95+
#define SUBMODULE_ERRORS_WARN 1 /* submodule fetch errors are non-fatal warnings */
96+
9397
int fetch_submodules(struct repository *r,
9498
const struct strvec *options,
9599
const char *prefix,
96100
int command_line_option,
97101
int default_option,
98-
int quiet, int max_parallel_jobs);
102+
int quiet, int max_parallel_jobs,
103+
int submodule_errors);
99104
unsigned is_submodule_modified(const char *path, int ignore_untracked);
100105
int submodule_uses_gitfile(const char *path);
101106

‎t/t5526-fetch-submodules.sh‎

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1307,6 +1307,57 @@ test_expect_success 'setup for submodule fetch error tests' '
13071307
git config --global protocol.file.allow always
13081308
'
13091309

1310+
test_expect_success 'fetch --recurse-submodules fails when submodule commit is unreachable (default)' '
1311+
test_when_finished "rm -fr env_default" &&
1312+
create_err_env env_default &&
1313+
push_unreachable_commit env_default &&
1314+
test_must_fail git -C env_default/clone fetch --recurse-submodules 2>err &&
1315+
test_grep "Errors during submodule fetch" err
1316+
'
1317+
1318+
test_expect_success 'fetch.submoduleErrors=warn: unreachable submodule commit is non-fatal' '
1319+
test_when_finished "rm -fr env_warn_cfg" &&
1320+
create_err_env env_warn_cfg &&
1321+
push_unreachable_commit env_warn_cfg &&
1322+
git -C env_warn_cfg/clone -c fetch.submoduleErrors=warn \
1323+
fetch --recurse-submodules 2>err &&
1324+
test_grep "Errors during submodule fetch" err
1325+
'
1326+
1327+
test_expect_success '--submodule-errors=warn: unreachable submodule commit is non-fatal' '
1328+
test_when_finished "rm -fr env_warn_cli" &&
1329+
create_err_env env_warn_cli &&
1330+
push_unreachable_commit env_warn_cli &&
1331+
git -C env_warn_cli/clone fetch --recurse-submodules \
1332+
--submodule-errors=warn 2>err &&
1333+
test_grep "Errors during submodule fetch" err
1334+
'
1335+
1336+
test_expect_success '--submodule-errors=fail: unreachable submodule commit is fatal' '
1337+
test_when_finished "rm -fr env_fail_cli" &&
1338+
create_err_env env_fail_cli &&
1339+
push_unreachable_commit env_fail_cli &&
1340+
test_must_fail git -C env_fail_cli/clone fetch --recurse-submodules \
1341+
--submodule-errors=fail 2>err &&
1342+
test_grep "Errors during submodule fetch" err
1343+
'
1344+
1345+
test_expect_success 'fetch.submoduleErrors=warn does not suppress successful fetch' '
1346+
# A new reachable submodule commit (pushed to sub_bare) should be
1347+
# fetched without any error summary.
1348+
test_when_finished "rm -fr env_ok" &&
1349+
create_err_env env_ok &&
1350+
test_commit -C env_ok/sub_work reachable_ok &&
1351+
git -C env_ok/sub_work push &&
1352+
git -C env_ok/super_work submodule update --remote &&
1353+
git -C env_ok/super_work add sub &&
1354+
git -C env_ok/super_work commit -m "point sub to reachable commit" &&
1355+
git -C env_ok/super_work push &&
1356+
git -C env_ok/clone -c fetch.submoduleErrors=warn \
1357+
fetch --recurse-submodules 2>err &&
1358+
test_grep ! "Errors during submodule fetch" err
1359+
'
1360+
13101361
test_expect_success 'failed submodule fetch is fatal even when its commits are present locally' '
13111362
# Create the same commit (unreferenced, via commit-tree with fixed
13121363
# dates) in both super_work/sub and clone/sub, point the gitlink at
@@ -1334,4 +1385,42 @@ test_expect_success 'failed submodule fetch is fatal even when its commits are p
13341385
test_grep "Errors during submodule fetch" err
13351386
'
13361387

1388+
test_expect_success '--submodule-errors=warn is honored by fetch --all' '
1389+
# A second remote forces fetch_multiple(), which hands the submodule
1390+
# recursion off to per-remote child processes; the option must be
1391+
# forwarded to them.
1392+
test_when_finished "rm -fr env_all" &&
1393+
create_err_env env_all &&
1394+
push_unreachable_commit env_all &&
1395+
git -C env_all/clone remote add second "$pwd/env_all/super_bare" &&
1396+
git -C env_all/clone fetch --all --recurse-submodules \
1397+
--submodule-errors=warn 2>err &&
1398+
test_grep "Errors during submodule fetch" err
1399+
'
1400+
1401+
test_expect_success '--submodule-errors=fail overrides warn config for fetch --all' '
1402+
# The per-remote child processes re-read the repository config, so
1403+
# the command-line override must be forwarded to them explicitly.
1404+
test_when_finished "rm -fr env_override" &&
1405+
create_err_env env_override &&
1406+
push_unreachable_commit env_override &&
1407+
git -C env_override/clone remote add second "$pwd/env_override/super_bare" &&
1408+
git -C env_override/clone config fetch.submoduleErrors warn &&
1409+
test_must_fail git -C env_override/clone fetch --all --recurse-submodules \
1410+
--submodule-errors=fail 2>err &&
1411+
test_grep "Errors during submodule fetch" err
1412+
'
1413+
1414+
test_expect_success 'fetch.submoduleErrors=warn: inaccessible submodule is non-fatal' '
1415+
test_when_finished "rm -fr env_access" &&
1416+
create_err_env env_access &&
1417+
rm env_access/clone/sub/.git &&
1418+
rm -r env_access/clone/.git/modules/sub &&
1419+
git -C env_access/clone -c fetch.submoduleErrors=warn \
1420+
fetch --recurse-submodules 2>err &&
1421+
test_grep "Could not access submodule" err &&
1422+
test_must_fail git -C env_access/clone fetch --recurse-submodules 2>err &&
1423+
test_grep "Could not access submodule" err
1424+
'
1425+
13371426
test_done

0 commit comments

Comments
 (0)