]> git.ipfire.org Git - thirdparty/git.git/commitdiff
Merge branch 'pz/fetch-submodule-errors-config' into seen
authorJunio C Hamano <gitster@pobox.com>
Thu, 30 Jul 2026 17:44:39 +0000 (10:44 -0700)
committerJunio C Hamano <gitster@pobox.com>
Thu, 30 Jul 2026 17:44:39 +0000 (10:44 -0700)
The 'git fetch' command has been updated to allow configuring how
submodule fetch errors are handled.  A new configuration variable
'fetch.submoduleErrors' and a corresponding '--submodule-errors'
command-line option have been introduced, allowing users to make
submodule fetch errors non-fatal (warn instead of fail).
Additionally, a premature failure during recursive submodule fetches
has been fixed by deferring the error until the OID-based retry phase
also fails.

* pz/fetch-submodule-errors-config:
  fetch: add fetch.submoduleErrors to make submodule fetch errors non-fatal
  submodule: fix premature failure in recursive submodule fetch

1  2 
Documentation/config/fetch.adoc
builtin/fetch.c
submodule.c
t/t5526-fetch-submodules.sh

Simple merge
diff --cc builtin/fetch.c
Simple merge
diff --cc submodule.c
Simple merge
index 7b3b7359da0108dc921b96000b11b95ebe9cc08b,19d17440cf2220d117f089f09bb608e96719131e..3456e366443f87a38a138989f621b70114670f81
@@@ -1259,7 -1259,168 +1259,168 @@@ test_expect_success "fetch --all with -
                git config submodule.recurse true &&
                git fetch --all --no-recurse-submodules 2>../fetch-log
        ) &&
 -      ! 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