Skip to content

Commit f1ae0e6

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 8ab95f4 commit f1ae0e6

6 files changed

Lines changed: 194 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: 71 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -110,8 +110,32 @@ 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_errors_names[] = {
118+
[SUBMODULE_ERRORS_FAIL] = "fail",
119+
[SUBMODULE_ERRORS_WARN] = "warn",
120+
};
121+
122+
static const char *submodule_errors_to_string(int mode)
123+
{
124+
if (mode < 0 || (size_t)mode >= ARRAY_SIZE(submodule_errors_names))
125+
BUG("invalid submodule errors mode %d", mode);
126+
return submodule_errors_names[mode];
127+
}
128+
129+
static int parse_submodule_errors(const char *name)
130+
{
131+
size_t i;
132+
133+
for (i = 0; i < ARRAY_SIZE(submodule_errors_names); i++)
134+
if (!strcmp(submodule_errors_names[i], name))
135+
return i;
136+
return -1;
137+
}
138+
115139
static int git_fetch_config(const char *k, const char *v,
116140
const struct config_context *ctx, void *cb)
117141
{
@@ -152,6 +176,19 @@ static int git_fetch_config(const char *k, const char *v,
152176
return 0;
153177
}
154178

179+
if (!strcmp(k, "fetch.submoduleerrors")) {
180+
int mode;
181+
182+
if (!v)
183+
return config_error_nonbool(k);
184+
mode = parse_submodule_errors(v);
185+
if (mode < 0)
186+
die(_("invalid value for '%s': '%s'"),
187+
"fetch.submoduleErrors", v);
188+
fetch_config->submodule_errors = mode;
189+
return 0;
190+
}
191+
155192
if (!strcmp(k, "fetch.parallel")) {
156193
fetch_config->parallel = git_config_int(k, v, ctx->kvi);
157194
if (fetch_config->parallel < 0)
@@ -2205,6 +2242,9 @@ static void add_options_to_argv(struct strvec *argv,
22052242
strvec_push(argv, "--no-recurse-submodules");
22062243
else if (config->recurse_submodules == RECURSE_SUBMODULES_ON_DEMAND)
22072244
strvec_push(argv, "--recurse-submodules=on-demand");
2245+
if (config->submodule_errors != -1)
2246+
strvec_pushf(argv, "--submodule-errors=%s",
2247+
submodule_errors_to_string(config->submodule_errors));
22082248
if (tags == TAGS_SET)
22092249
strvec_push(argv, "--tags");
22102250
else if (tags == TAGS_UNSET)
@@ -2464,6 +2504,23 @@ static int fetch_one(struct remote *remote, int argc, const char **argv,
24642504
return exit_code;
24652505
}
24662506

2507+
static int option_parse_submodule_errors(const struct option *opt,
2508+
const char *arg, int unset)
2509+
{
2510+
int *v = opt->value;
2511+
int mode;
2512+
2513+
if (unset) {
2514+
*v = SUBMODULE_ERRORS_FAIL;
2515+
return 0;
2516+
}
2517+
mode = parse_submodule_errors(arg);
2518+
if (mode < 0)
2519+
die(_("invalid value for '%s': '%s'"), "--submodule-errors", arg);
2520+
*v = mode;
2521+
return 0;
2522+
}
2523+
24672524
int cmd_fetch(int argc,
24682525
const char **argv,
24692526
const char *prefix,
@@ -2477,6 +2534,7 @@ int cmd_fetch(int argc,
24772534
.recurse_submodules = RECURSE_SUBMODULES_DEFAULT,
24782535
.parallel = 1,
24792536
.submodule_fetch_jobs = -1,
2537+
.submodule_errors = -1, /* unset */
24802538
};
24812539
const char *submodule_prefix = "";
24822540
const char *bundle_uri;
@@ -2491,6 +2549,7 @@ int cmd_fetch(int argc,
24912549
int max_jobs = -1;
24922550
int recurse_submodules_cli = RECURSE_SUBMODULES_DEFAULT;
24932551
int recurse_submodules_default = RECURSE_SUBMODULES_ON_DEMAND;
2552+
int submodule_errors_cli = -1; /* -1: not set on command line */
24942553
int fetch_write_commit_graph = -1;
24952554
int stdin_refspecs = 0;
24962555
int negotiate_only = 0;
@@ -2527,6 +2586,10 @@ int cmd_fetch(int argc,
25272586
OPT_CALLBACK_F(0, "recurse-submodules", &recurse_submodules_cli, N_("on-demand"),
25282587
N_("control recursive fetching of submodules"),
25292588
PARSE_OPT_OPTARG, option_fetch_parse_recurse_submodules),
2589+
OPT_CALLBACK_F(0, "submodule-errors", &submodule_errors_cli,
2590+
N_("(fail|warn)"),
2591+
N_("control how submodule fetch errors are handled"),
2592+
0, option_parse_submodule_errors),
25302593
OPT_BOOL(0, "dry-run", &dry_run,
25312594
N_("dry run")),
25322595
OPT_BOOL(0, "porcelain", &porcelain, N_("machine-readable output")),
@@ -2616,6 +2679,9 @@ int cmd_fetch(int argc,
26162679
if (recurse_submodules_cli != RECURSE_SUBMODULES_DEFAULT)
26172680
config.recurse_submodules = recurse_submodules_cli;
26182681

2682+
if (submodule_errors_cli != -1)
2683+
config.submodule_errors = submodule_errors_cli;
2684+
26192685
if (negotiate_only) {
26202686
switch (recurse_submodules_cli) {
26212687
case RECURSE_SUBMODULES_OFF:
@@ -2819,11 +2885,14 @@ int cmd_fetch(int argc,
28192885
if (!result && remote && (config.recurse_submodules != RECURSE_SUBMODULES_OFF)) {
28202886
struct strvec options = STRVEC_INIT;
28212887
int max_children = max_jobs;
2888+
int submodule_errors = config.submodule_errors;
28222889

28232890
if (max_children < 0)
28242891
max_children = config.submodule_fetch_jobs;
28252892
if (max_children < 0)
28262893
max_children = config.parallel;
2894+
if (submodule_errors < 0)
2895+
submodule_errors = SUBMODULE_ERRORS_FAIL;
28272896

28282897
add_options_to_argv(&options, &config);
28292898
trace2_region_enter_printf("fetch", "recurse-submodule", the_repository, "%s", submodule_prefix);
@@ -2833,7 +2902,8 @@ int cmd_fetch(int argc,
28332902
config.recurse_submodules,
28342903
recurse_submodules_default,
28352904
verbosity < 0,
2836-
max_children);
2905+
max_children,
2906+
submodule_errors);
28372907
trace2_region_leave_printf("fetch", "recurse-submodule", the_repository, "%s", submodule_prefix);
28382908
strvec_clear(&options);
28392909
}

‎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)