From 46232648091f5490ac31eba2ec54c8b9c20729bf Mon Sep 17 00:00:00 2001 From: Kaushik Lingarkar Date: Wed, 10 Sep 2025 17:07:35 -0700 Subject: Fix submodule initialization in interleaved sync mode MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 Tested-by: Kaushik Lingarkar Reviewed-by: Nasser Grainawi Commit-Queue: Kaushik Lingarkar Reviewed-by: Scott Lee --- project.py | 45 +++++++++++++++++++++++++++++++-------------- 1 file changed, 31 insertions(+), 14 deletions(-) (limited to 'project.py') diff --git a/project.py b/project.py index 4379be6c..dfc20098 100644 --- a/project.py +++ b/project.py @@ -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"] -- cgit v1.2.3-54-g00ecf