From: Paulius Zaleckas <paulius.zaleckas@gmail.com>
To: git@vger.kernel.org
Cc: Paulius Zaleckas <paulius.zaleckas@gmail.com>
Subject: [PATCH 1/2] submodule: fix premature failure in recursive submodule fetch
Date: Mon, 30 Mar 2026 20:39:37 +0300 [thread overview]
Message-ID: <20260330173938.3792358-2-paulius.zaleckas@gmail.com> (raw)
In-Reply-To: <20260330173938.3792358-1-paulius.zaleckas@gmail.com>
When git fetch --recurse-submodules encounters a failure fetching a
submodule's refs (phase 1), it immediately marks the overall operation
as failed, even though a subsequent OID-based fetch (phase 2) is about
to be attempted for any missing commits. If phase 2 succeeds, the
overall result should be success, but the prematurely set failure flag
makes it look like an error.
Restructure fetch_finish() so that a phase-1 failure does not record an
error immediately. Instead, the decision is deferred:
- If missing commits trigger a phase-2 (OID-based) retry and that
retry succeeds, no error is recorded.
- If the phase-2 retry also fails, the error is recorded then.
- If the submodule was fetched unconditionally (RECURSE_SUBMODULES_ON)
and is not in the changed list, a phase-1 failure is recorded right
away since there is no OID retry to fall back on.
- If phase 1 fails but all required commits are already present
locally, no error is recorded.
This resolves the NEEDSWORK comment added by bd5e567dc7 (submodule:
explain first attempt failure clearly, 2019-03-13).
Extract the common error-recording logic into a helper
record_fetch_error() and use it in fetch_start_failure() as well.
Signed-off-by: Paulius Zaleckas <paulius.zaleckas@gmail.com>
---
submodule.c | 46 +++++++++++++++++++++++++++++++++-------------
1 file changed, 33 insertions(+), 13 deletions(-)
diff --git a/submodule.c b/submodule.c
index e20537ba8d..0fda96a436 100644
--- a/submodule.c
+++ b/submodule.c
@@ -1746,13 +1746,20 @@ static int get_next_submodule(struct child_process *cp, struct strbuf *err,
return 0;
}
+static void record_fetch_error(struct submodule_parallel_fetch *spf,
+ const char *name)
+{
+ spf->result = 1;
+ strbuf_addf(&spf->submodules_with_errors, "\t%s\n", name);
+}
+
static int fetch_start_failure(struct strbuf *err UNUSED,
void *cb, void *task_cb)
{
struct submodule_parallel_fetch *spf = cb;
struct fetch_task *task = task_cb;
- spf->result = 1;
+ record_fetch_error(spf, task->sub->name);
fetch_task_free(task);
return 0;
@@ -1778,18 +1785,12 @@ static int fetch_finish(int retvalue, struct strbuf *err UNUSED,
if (!task || !task->sub)
BUG("callback cookie bogus");
- if (retvalue) {
+ if (retvalue && task->commits) {
/*
- * NEEDSWORK: This indicates that the overall fetch
- * failed, even though there may be a subsequent fetch
- * 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.
+ * This is the second pass (OID-based fetch) and it failed.
+ * The commits are genuinely unavailable from the remote.
*/
- spf->result = 1;
-
- strbuf_addf(&spf->submodules_with_errors, "\t%s\n",
- task->sub->name);
+ record_fetch_error(spf, task->sub->name);
}
/* Is this the second time we process this submodule? */
@@ -1797,9 +1798,17 @@ static int fetch_finish(int retvalue, struct strbuf *err UNUSED,
goto out;
it = string_list_lookup(&spf->changed_submodule_names, task->sub->name);
- if (!it)
- /* Could be an unchanged submodule, not contained in the list */
+ if (!it) {
+ /*
+ * 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;
+ }
cs_data = it->util;
oid_array_filter(&cs_data->new_commits,
@@ -1808,6 +1817,11 @@ static int fetch_finish(int retvalue, struct strbuf *err UNUSED,
/* Are there commits we want, but do not exist? */
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;
ALLOC_GROW(spf->oid_fetch_tasks,
spf->oid_fetch_tasks_nr + 1,
@@ -1817,6 +1831,12 @@ static int fetch_finish(int retvalue, struct strbuf *err UNUSED,
return 0;
}
+ /*
+ * All required commits are already present locally (they were either
+ * fetched by phase 1 or existed beforehand). Even if phase 1 failed
+ * for other reasons, there is nothing left to fetch.
+ */
+
out:
fetch_task_free(task);
return 0;
--
2.53.0
next prev parent reply other threads:[~2026-03-30 17:41 UTC|newest]
Thread overview: 4+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-03-30 17:39 [PATCH 0/2] fetch: make submodule fetch errors configurable Paulius Zaleckas
2026-03-30 17:39 ` Paulius Zaleckas [this message]
2026-03-30 17:39 ` [PATCH 2/2] fetch: add fetch.submoduleErrors to make submodule fetch errors non-fatal Paulius Zaleckas
2026-03-31 16:44 ` Jean-Noël AVILA
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20260330173938.3792358-2-paulius.zaleckas@gmail.com \
--to=paulius.zaleckas@gmail.com \
--cc=git@vger.kernel.org \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox