project: don't plan a stateless prune without a worktree 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>
diff --git a/project.py b/project.py index 6fce0e4..a2786e4 100644 --- a/project.py +++ b/project.py
@@ -1667,6 +1667,12 @@ if not self.Exists: return False + # Local changes can't be checked without a worktree, and pruning drops + # every reflog. Sync_LocalHalf() recreates the worktree, so a later + # sync can decide. + if not self.worktree or not platform_utils.isdir(self.worktree): + return False + if self._CheckForImmutableRevision(use_superproject=use_superproject): return False @@ -3914,9 +3920,9 @@ f"{self.name} cherry-pick {rev} ", project=self.name ) - def _LsRemote(self, refs): + def _LsRemote(self, refs: str) -> Optional[str]: cmd = ["ls-remote", self.remote.name, refs] - p = GitCommand(self, cmd, capture_stdout=True) + p = GitCommand(self, cmd, bare=True, capture_stdout=True) if p.Wait() == 0: return p.stdout return None
diff --git a/tests/test_project.py b/tests/test_project.py index 46bc7f1..249cbd0 100644 --- a/tests/test_project.py +++ b/tests/test_project.py
@@ -2420,6 +2420,33 @@ self.assertTrue(res.success) self.assertFalse(getattr(proj, "stateless_prune_needed", False)) + def test_sync_network_half_stateless_skips_without_worktree(self) -> None: + """Test stateless sync doesn't prune a project with no worktree.""" + with utils_for_test.TempGitTree() as tempdir: + proj = self._get_project(tempdir) + proj.worktree = os.path.join(tempdir, "missing") + proj._HasDirtyOrStash = mock.MagicMock(return_value=False) + + res = proj.Sync_NetworkHalf() + + self.assertTrue(res.success) + self.assertFalse(proj.stateless_prune_needed) + proj._LsRemote.assert_not_called() + proj._HasDirtyOrStash.assert_not_called() + + def test_ls_remote_runs_without_worktree(self) -> None: + """Test ls-remote only needs the gitdir's remote config.""" + with utils_for_test.TempGitTree() as tempdir: + proj = _create_mock_project(tempdir) + proj.work_git.commit("--allow-empty", "-m", "initial") + proj.work_git.config("remote.origin.url", tempdir) + head = proj.work_git.rev_parse("HEAD") + proj.worktree = os.path.join(tempdir, "missing") + # _create_mock_project() stubs this out. + del proj._LsRemote + + self.assertEqual(f"{head}\tHEAD\n", proj._LsRemote("HEAD")) + def test_sync_network_half_stateless_skips_if_local_commits(self): """Test stateless sync skips if there are local-only commits.""" with utils_for_test.TempGitTree() as tempdir: