diff --git a/subcmds/sync.py b/subcmds/sync.py index 02c1d3ae..b7236629 100644 --- a/subcmds/sync.py +++ b/subcmds/sync.py @@ -2011,7 +2011,7 @@ class LocalSyncState: delete = set() for path in self._state: gitdir = os.path.join(self._manifest.topdir, path, ".git") - if not os.path.exists(gitdir): + if not os.path.exists(gitdir) or os.path.islink(gitdir): delete.add(path) if not delete: return diff --git a/tests/test_subcmds_sync.py b/tests/test_subcmds_sync.py index 71e40489..af6bbef7 100644 --- a/tests/test_subcmds_sync.py +++ b/tests/test_subcmds_sync.py @@ -265,6 +265,44 @@ class LocalSyncState(unittest.TestCase): self.assertIsNone(self.state.GetFetchTime(projA)) self.assertEqual(self.state.GetFetchTime(projB), 7) + def test_prune_removed_and_symlinked_projects(self): + """Removed projects that still exists on disk as symlink are pruned.""" + with open(self.state._path, "w") as f: + f.write( + """ + { + "projA": { + "last_fetch": 5 + }, + "projB": { + "last_fetch": 7 + } + } + """ + ) + + def mock_exists(path): + return True + + def mock_islink(path): + if "projB" in path: + return True + return False + + projA = mock.MagicMock(relpath="projA") + projB = mock.MagicMock(relpath="projB") + self.state = self._new_state() + self.assertEqual(self.state.GetFetchTime(projA), 5) + self.assertEqual(self.state.GetFetchTime(projB), 7) + with mock.patch("os.path.exists", side_effect=mock_exists): + with mock.patch("os.path.islink", side_effect=mock_islink): + self.state.PruneRemovedProjects() + self.assertIsNone(self.state.GetFetchTime(projB)) + + self.state = self._new_state() + self.assertIsNone(self.state.GetFetchTime(projB)) + self.assertEqual(self.state.GetFetchTime(projA), 5) + class GetPreciousObjectsState(unittest.TestCase): """Tests for _GetPreciousObjectsState."""