diff options
| author | Gavin Mak <gavinmak@google.com> | 2026-09-23 10:42:00 -0700 |
|---|---|---|
| committer | gerrit-scoped@luci-project-accounts.iam.gserviceaccount.com <gerrit-scoped@luci-project-accounts.iam.gserviceaccount.com> | 2026-09-23 13:27:06 -0700 |
| commit | e1e215a14ea5373419acb8753b6865de19d1f122 (patch) | |
| tree | 88446b761a72901bafe5dd1b785b47244c5483ec | |
| parent | 175198b0aa22a54406be2b651020bb53c3c6d2b7 (diff) | |
| download | git-repo-2.68.1.tar.gz | |
project: don't plan a stateless prune without a worktreev2.68.1
Skip stateless prune planning when a project's worktree directory
does not exist, preventing fetch failures on subsequent syncs.
Also run _LsRemote() bare so it does not depend on worktree
existence.
Bug: 565047698
Change-Id: I385cba4e06813d9113ec7ddc963e8499acffaec5
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/634426
Tested-by: Gavin Mak <gavinmak@google.com>
Commit-Queue: Gavin Mak <gavinmak@google.com>
Reviewed-by: Brian Gan <brgan@google.com>
| -rw-r--r-- | project.py | 10 | ||||
| -rw-r--r-- | tests/test_project.py | 27 |
2 files changed, 35 insertions, 2 deletions
diff --git a/project.py b/project.py index 6fce0e442..a2786e4b5 100644 --- a/project.py +++ b/project.py | |||
| @@ -1667,6 +1667,12 @@ class Project: | |||
| 1667 | if not self.Exists: | 1667 | if not self.Exists: |
| 1668 | return False | 1668 | return False |
| 1669 | 1669 | ||
| 1670 | # Local changes can't be checked without a worktree, and pruning drops | ||
| 1671 | # every reflog. Sync_LocalHalf() recreates the worktree, so a later | ||
| 1672 | # sync can decide. | ||
| 1673 | if not self.worktree or not platform_utils.isdir(self.worktree): | ||
| 1674 | return False | ||
| 1675 | |||
| 1670 | if self._CheckForImmutableRevision(use_superproject=use_superproject): | 1676 | if self._CheckForImmutableRevision(use_superproject=use_superproject): |
| 1671 | return False | 1677 | return False |
| 1672 | 1678 | ||
| @@ -3914,9 +3920,9 @@ class Project: | |||
| 3914 | f"{self.name} cherry-pick {rev} ", project=self.name | 3920 | f"{self.name} cherry-pick {rev} ", project=self.name |
| 3915 | ) | 3921 | ) |
| 3916 | 3922 | ||
| 3917 | def _LsRemote(self, refs): | 3923 | def _LsRemote(self, refs: str) -> Optional[str]: |
| 3918 | cmd = ["ls-remote", self.remote.name, refs] | 3924 | cmd = ["ls-remote", self.remote.name, refs] |
| 3919 | p = GitCommand(self, cmd, capture_stdout=True) | 3925 | p = GitCommand(self, cmd, bare=True, capture_stdout=True) |
| 3920 | if p.Wait() == 0: | 3926 | if p.Wait() == 0: |
| 3921 | return p.stdout | 3927 | return p.stdout |
| 3922 | return None | 3928 | return None |
diff --git a/tests/test_project.py b/tests/test_project.py index 46bc7f1fa..249cbd072 100644 --- a/tests/test_project.py +++ b/tests/test_project.py | |||
| @@ -2420,6 +2420,33 @@ class StatelessSyncTests(unittest.TestCase): | |||
| 2420 | self.assertTrue(res.success) | 2420 | self.assertTrue(res.success) |
| 2421 | self.assertFalse(getattr(proj, "stateless_prune_needed", False)) | 2421 | self.assertFalse(getattr(proj, "stateless_prune_needed", False)) |
| 2422 | 2422 | ||
| 2423 | def test_sync_network_half_stateless_skips_without_worktree(self) -> None: | ||
| 2424 | """Test stateless sync doesn't prune a project with no worktree.""" | ||
| 2425 | with utils_for_test.TempGitTree() as tempdir: | ||
| 2426 | proj = self._get_project(tempdir) | ||
| 2427 | proj.worktree = os.path.join(tempdir, "missing") | ||
| 2428 | proj._HasDirtyOrStash = mock.MagicMock(return_value=False) | ||
| 2429 | |||
| 2430 | res = proj.Sync_NetworkHalf() | ||
| 2431 | |||
| 2432 | self.assertTrue(res.success) | ||
| 2433 | self.assertFalse(proj.stateless_prune_needed) | ||
| 2434 | proj._LsRemote.assert_not_called() | ||
| 2435 | proj._HasDirtyOrStash.assert_not_called() | ||
| 2436 | |||
| 2437 | def test_ls_remote_runs_without_worktree(self) -> None: | ||
| 2438 | """Test ls-remote only needs the gitdir's remote config.""" | ||
| 2439 | with utils_for_test.TempGitTree() as tempdir: | ||
| 2440 | proj = _create_mock_project(tempdir) | ||
| 2441 | proj.work_git.commit("--allow-empty", "-m", "initial") | ||
| 2442 | proj.work_git.config("remote.origin.url", tempdir) | ||
| 2443 | head = proj.work_git.rev_parse("HEAD") | ||
| 2444 | proj.worktree = os.path.join(tempdir, "missing") | ||
| 2445 | # _create_mock_project() stubs this out. | ||
| 2446 | del proj._LsRemote | ||
| 2447 | |||
| 2448 | self.assertEqual(f"{head}\tHEAD\n", proj._LsRemote("HEAD")) | ||
| 2449 | |||
| 2423 | def test_sync_network_half_stateless_skips_if_local_commits(self): | 2450 | def test_sync_network_half_stateless_skips_if_local_commits(self): |
| 2424 | """Test stateless sync skips if there are local-only commits.""" | 2451 | """Test stateless sync skips if there are local-only commits.""" |
| 2425 | with utils_for_test.TempGitTree() as tempdir: | 2452 | with utils_for_test.TempGitTree() as tempdir: |
