diff options
| author | Kaushik Lingarkar <kaushikl@qti.qualcomm.com> | 2025-09-10 17:07:35 -0700 |
|---|---|---|
| committer | LUCI <gerrit-scoped@luci-project-accounts.iam.gserviceaccount.com> | 2025-10-14 12:07:04 -0700 |
| commit | 46232648091f5490ac31eba2ec54c8b9c20729bf (patch) | |
| tree | 411ac0a4f8ad30980381be4305fbfe10214ed1d4 | |
| parent | 67383bdba9105203ffeaef645fa8c53b7ca33ac8 (diff) | |
| download | git-repo-46232648091f5490ac31eba2ec54c8b9c20729bf.tar.gz | |
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>
| -rw-r--r-- | project.py | 45 |
1 files changed, 31 insertions, 14 deletions
| @@ -642,10 +642,6 @@ class Project: | |||
| 642 | # project containing repo hooks. | 642 | # project containing repo hooks. |
| 643 | self.enabled_repo_hooks = [] | 643 | self.enabled_repo_hooks = [] |
| 644 | 644 | ||
| 645 | # This will be updated later if the project has submodules and | ||
| 646 | # if they will be synced. | ||
| 647 | self.has_subprojects = False | ||
| 648 | |||
| 649 | def RelPath(self, local=True): | 645 | def RelPath(self, local=True): |
| 650 | """Return the path for the project relative to a manifest. | 646 | """Return the path for the project relative to a manifest. |
| 651 | 647 | ||
| @@ -1563,8 +1559,8 @@ class Project: | |||
| 1563 | # TODO(https://git-scm.com/docs/git-worktree#_bugs): Re-evaluate if | 1559 | # TODO(https://git-scm.com/docs/git-worktree#_bugs): Re-evaluate if |
| 1564 | # submodules can be init when using worktrees once its support is | 1560 | # submodules can be init when using worktrees once its support is |
| 1565 | # complete. | 1561 | # complete. |
| 1566 | if self.has_subprojects and not self.use_git_worktrees: | 1562 | if self.parent and not self.use_git_worktrees: |
| 1567 | self._InitSubmodules() | 1563 | self._InitSubmodule() |
| 1568 | all_refs = self.bare_ref.all | 1564 | all_refs = self.bare_ref.all |
| 1569 | self.CleanPublishedCache(all_refs) | 1565 | self.CleanPublishedCache(all_refs) |
| 1570 | revid = self.GetRevisionId(all_refs) | 1566 | revid = self.GetRevisionId(all_refs) |
| @@ -2359,8 +2355,6 @@ class Project: | |||
| 2359 | ) | 2355 | ) |
| 2360 | result.append(subproject) | 2356 | result.append(subproject) |
| 2361 | result.extend(subproject.GetDerivedSubprojects()) | 2357 | result.extend(subproject.GetDerivedSubprojects()) |
| 2362 | if result: | ||
| 2363 | self.has_subprojects = True | ||
| 2364 | return result | 2358 | return result |
| 2365 | 2359 | ||
| 2366 | def EnableRepositoryExtension(self, key, value="true", version=1): | 2360 | def EnableRepositoryExtension(self, key, value="true", version=1): |
| @@ -3030,16 +3024,39 @@ class Project: | |||
| 3030 | project=self.name, | 3024 | project=self.name, |
| 3031 | ) | 3025 | ) |
| 3032 | 3026 | ||
| 3033 | def _InitSubmodules(self, quiet=True): | 3027 | def _InitSubmodule(self, quiet=True): |
| 3034 | """Initialize the submodules for the project.""" | 3028 | """Initialize the submodule.""" |
| 3035 | cmd = ["submodule", "init"] | 3029 | cmd = ["submodule", "init"] |
| 3036 | if quiet: | 3030 | if quiet: |
| 3037 | cmd.append("-q") | 3031 | cmd.append("-q") |
| 3038 | if GitCommand(self, cmd).Wait() != 0: | 3032 | cmd.extend(["--", self.worktree]) |
| 3039 | raise GitError( | 3033 | max_retries = 3 |
| 3040 | f"{self.name} submodule init", | 3034 | base_delay_secs = 1 |
| 3041 | project=self.name, | 3035 | jitter_ratio = 1 / 3 |
| 3036 | for attempt in range(max_retries): | ||
| 3037 | git_cmd = GitCommand( | ||
| 3038 | None, | ||
| 3039 | cmd, | ||
| 3040 | cwd=self.parent.worktree, | ||
| 3041 | capture_stdout=True, | ||
| 3042 | capture_stderr=True, | ||
| 3042 | ) | 3043 | ) |
| 3044 | if git_cmd.Wait() == 0: | ||
| 3045 | return | ||
| 3046 | error = git_cmd.stderr or git_cmd.stdout | ||
| 3047 | if "lock" in error: | ||
| 3048 | delay = base_delay_secs * (2**attempt) | ||
| 3049 | delay += random.uniform(0, delay * jitter_ratio) | ||
| 3050 | logger.warning( | ||
| 3051 | f"Attempt {attempt+1}/{max_retries}: " | ||
| 3052 | + f"git {' '.join(cmd)} failed." | ||
| 3053 | + f" Error: {error}." | ||
| 3054 | + f" Sleeping {delay:.2f}s before retrying." | ||
| 3055 | ) | ||
| 3056 | time.sleep(delay) | ||
| 3057 | else: | ||
| 3058 | break | ||
| 3059 | git_cmd.VerifyCommand() | ||
| 3043 | 3060 | ||
| 3044 | def _Rebase(self, upstream, onto=None): | 3061 | def _Rebase(self, upstream, onto=None): |
| 3045 | cmd = ["rebase"] | 3062 | cmd = ["rebase"] |
