Fix submodule initialization in interleaved sync mode

With the introduction of interleaved sync mode, the submodule activation
logic broke because the 'has_submodules' attribute was no longer being
populated when needed. With this change, each submodule is initialized
when it enters the Sync_LocalHalf stage, whereas previously all
submodules were initialized at once when the parent repository entered
the Sync_LocalHalf stage. The init is now retried if it fails, as
submodules may concurrently modify the parent’s git config, potentially
causing contention when attempting to obtain a lock on it.

This change makes the submodule activation logic more robust and less
prone to breakage.

Bug: 444366154
Change-Id: I25eca4ea2a6868219045cfa088988eb01ded47d2
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/509041
Reviewed-by: Gavin Mak <gavinmak@google.com>
Tested-by: Kaushik Lingarkar <kaushikl@qti.qualcomm.com>
Reviewed-by: Nasser Grainawi <nasser.grainawi@oss.qualcomm.com>
Commit-Queue: Kaushik Lingarkar <kaushikl@qti.qualcomm.com>
Reviewed-by: Scott Lee <ddoman@google.com>
This commit is contained in:
Kaushik Lingarkar
2025-09-10 17:07:35 -07:00
committed by LUCI
parent 67383bdba9
commit 4623264809

View File

@@ -642,10 +642,6 @@ class Project:
# project containing repo hooks.
self.enabled_repo_hooks = []
# This will be updated later if the project has submodules and
# if they will be synced.
self.has_subprojects = False
def RelPath(self, local=True):
"""Return the path for the project relative to a manifest.
@@ -1563,8 +1559,8 @@ class Project:
# TODO(https://git-scm.com/docs/git-worktree#_bugs): Re-evaluate if
# submodules can be init when using worktrees once its support is
# complete.
if self.has_subprojects and not self.use_git_worktrees:
self._InitSubmodules()
if self.parent and not self.use_git_worktrees:
self._InitSubmodule()
all_refs = self.bare_ref.all
self.CleanPublishedCache(all_refs)
revid = self.GetRevisionId(all_refs)
@@ -2359,8 +2355,6 @@ class Project:
)
result.append(subproject)
result.extend(subproject.GetDerivedSubprojects())
if result:
self.has_subprojects = True
return result
def EnableRepositoryExtension(self, key, value="true", version=1):
@@ -3030,16 +3024,39 @@ class Project:
project=self.name,
)
def _InitSubmodules(self, quiet=True):
"""Initialize the submodules for the project."""
def _InitSubmodule(self, quiet=True):
"""Initialize the submodule."""
cmd = ["submodule", "init"]
if quiet:
cmd.append("-q")
if GitCommand(self, cmd).Wait() != 0:
raise GitError(
f"{self.name} submodule init",
project=self.name,
cmd.extend(["--", self.worktree])
max_retries = 3
base_delay_secs = 1
jitter_ratio = 1 / 3
for attempt in range(max_retries):
git_cmd = GitCommand(
None,
cmd,
cwd=self.parent.worktree,
capture_stdout=True,
capture_stderr=True,
)
if git_cmd.Wait() == 0:
return
error = git_cmd.stderr or git_cmd.stdout
if "lock" in error:
delay = base_delay_secs * (2**attempt)
delay += random.uniform(0, delay * jitter_ratio)
logger.warning(
f"Attempt {attempt+1}/{max_retries}: "
+ f"git {' '.join(cmd)} failed."
+ f" Error: {error}."
+ f" Sleeping {delay:.2f}s before retrying."
)
time.sleep(delay)
else:
break
git_cmd.VerifyCommand()
def _Rebase(self, upstream, onto=None):
cmd = ["rebase"]