diff options
| author | Gavin Mak <gavinmak@google.com> | 2026-04-20 17:57:20 +0000 |
|---|---|---|
| committer | gerrit-scoped@luci-project-accounts.iam.gserviceaccount.com <gerrit-scoped@luci-project-accounts.iam.gserviceaccount.com> | 2026-04-20 15:34:07 -0700 |
| commit | baa281d99e59dbf1447524e6fd95b384cadbc06e (patch) | |
| tree | 412dab50845510a6b4a802638c7d055955e37505 | |
| parent | 7e9079b7cf79988dd978d04bfb79250564829cc3 (diff) | |
| download | git-repo-stable.tar.gz | |
Extract _RunOneGC to handle GC on a single project. This refactoring
makes it easier to invoke GC from parallel worker tasks.
Also, avoid modifying the passed-in config dictionary in _RunOneGC by
creating a local copy, preventing unintended side effects on other
commands sharing the same config.
Bug: 498290329
Change-Id: I7b77ed6629b14b5ee3322870b9c6c8ce2bfd6ea2
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/574923
Reviewed-by: Becky Siegel <beckysiegel@google.com>
Tested-by: Gavin Mak <gavinmak@google.com>
Commit-Queue: Gavin Mak <gavinmak@google.com>
| -rw-r--r-- | subcmds/sync.py | 31 | ||||
| -rw-r--r-- | tests/test_subcmds_sync.py | 37 |
2 files changed, 42 insertions, 26 deletions
diff --git a/subcmds/sync.py b/subcmds/sync.py index 85ead6c31..bfbe1937d 100644 --- a/subcmds/sync.py +++ b/subcmds/sync.py | |||
| @@ -1244,14 +1244,15 @@ later is required to fix a server side protocol bug. | |||
| 1244 | 1244 | ||
| 1245 | return False | 1245 | return False |
| 1246 | 1246 | ||
| 1247 | def _SetPreciousObjectsState(self, project: Project, opt): | 1247 | @classmethod |
| 1248 | def _SetPreciousObjectsState(cls, project: Project, opt): | ||
| 1248 | """Correct the preciousObjects state for the project. | 1249 | """Correct the preciousObjects state for the project. |
| 1249 | 1250 | ||
| 1250 | Args: | 1251 | Args: |
| 1251 | project: the project to examine, and possibly correct. | 1252 | project: the project to examine, and possibly correct. |
| 1252 | opt: options given to sync. | 1253 | opt: options given to sync. |
| 1253 | """ | 1254 | """ |
| 1254 | expected = self._GetPreciousObjectsState(project, opt) | 1255 | expected = cls._GetPreciousObjectsState(project, opt) |
| 1255 | actual = ( | 1256 | actual = ( |
| 1256 | project.config.GetBoolean("extensions.preciousObjects") or False | 1257 | project.config.GetBoolean("extensions.preciousObjects") or False |
| 1257 | ) | 1258 | ) |
| @@ -1285,7 +1286,22 @@ later is required to fix a server side protocol bug. | |||
| 1285 | project.config.SetString("extensions.preciousObjects", None) | 1286 | project.config.SetString("extensions.preciousObjects", None) |
| 1286 | project.config.SetString("gc.pruneExpire", None) | 1287 | project.config.SetString("gc.pruneExpire", None) |
| 1287 | 1288 | ||
| 1288 | def _GCProjects(self, projects, opt, err_event): | 1289 | @staticmethod |
| 1290 | def _RunOneGC(project: Project, config: Optional[dict] = None) -> None: | ||
| 1291 | """Run auto GC on a single project.""" | ||
| 1292 | local_config = {} | ||
| 1293 | if config: | ||
| 1294 | local_config.update(config) | ||
| 1295 | local_config["gc.autoDetach"] = "false" | ||
| 1296 | project.bare_git.gc("--auto", config=local_config) | ||
| 1297 | |||
| 1298 | @classmethod | ||
| 1299 | def _GCProjects( | ||
| 1300 | cls, | ||
| 1301 | projects: List[Project], | ||
| 1302 | opt: optparse.Values, | ||
| 1303 | err_event: _threading.Event, | ||
| 1304 | ) -> None: | ||
| 1289 | """Perform garbage collection. | 1305 | """Perform garbage collection. |
| 1290 | 1306 | ||
| 1291 | If We are skipping garbage collection (opt.auto_gc not set), we still | 1307 | If We are skipping garbage collection (opt.auto_gc not set), we still |
| @@ -1295,7 +1311,7 @@ later is required to fix a server side protocol bug. | |||
| 1295 | if not opt.auto_gc: | 1311 | if not opt.auto_gc: |
| 1296 | # Just repair preciousObjects state, and return. | 1312 | # Just repair preciousObjects state, and return. |
| 1297 | for project in projects: | 1313 | for project in projects: |
| 1298 | self._SetPreciousObjectsState(project, opt) | 1314 | cls._SetPreciousObjectsState(project, opt) |
| 1299 | return | 1315 | return |
| 1300 | 1316 | ||
| 1301 | pm = Progress( | 1317 | pm = Progress( |
| @@ -1305,9 +1321,8 @@ later is required to fix a server side protocol bug. | |||
| 1305 | 1321 | ||
| 1306 | tidy_dirs = {} | 1322 | tidy_dirs = {} |
| 1307 | for project in projects: | 1323 | for project in projects: |
| 1308 | self._SetPreciousObjectsState(project, opt) | 1324 | cls._SetPreciousObjectsState(project, opt) |
| 1309 | 1325 | ||
| 1310 | project.config.SetString("gc.autoDetach", "false") | ||
| 1311 | # Only call git gc once per objdir, but call pack-refs for the | 1326 | # Only call git gc once per objdir, but call pack-refs for the |
| 1312 | # remainder. | 1327 | # remainder. |
| 1313 | if project.objdir not in tidy_dirs: | 1328 | if project.objdir not in tidy_dirs: |
| @@ -1328,7 +1343,7 @@ later is required to fix a server side protocol bug. | |||
| 1328 | pm.update(msg=bare_git._project.name) | 1343 | pm.update(msg=bare_git._project.name) |
| 1329 | 1344 | ||
| 1330 | if run_gc: | 1345 | if run_gc: |
| 1331 | bare_git.gc("--auto") | 1346 | cls._RunOneGC(bare_git._project) |
| 1332 | else: | 1347 | else: |
| 1333 | bare_git.pack_refs() | 1348 | bare_git.pack_refs() |
| 1334 | pm.end() | 1349 | pm.end() |
| @@ -1345,7 +1360,7 @@ later is required to fix a server side protocol bug. | |||
| 1345 | try: | 1360 | try: |
| 1346 | try: | 1361 | try: |
| 1347 | if run_gc: | 1362 | if run_gc: |
| 1348 | bare_git.gc("--auto", config=config) | 1363 | cls._RunOneGC(bare_git._project, config=config) |
| 1349 | else: | 1364 | else: |
| 1350 | bare_git.pack_refs(config=config) | 1365 | bare_git.pack_refs(config=config) |
| 1351 | except GitError: | 1366 | except GitError: |
diff --git a/tests/test_subcmds_sync.py b/tests/test_subcmds_sync.py index a6be0807f..b3726d2d1 100644 --- a/tests/test_subcmds_sync.py +++ b/tests/test_subcmds_sync.py | |||
| @@ -552,35 +552,36 @@ class GCProjectsTest(unittest.TestCase): | |||
| 552 | def test_GCProjects_skip_gc(self, mock_progress): | 552 | def test_GCProjects_skip_gc(self, mock_progress): |
| 553 | """Test that it skips GC if opt.auto_gc is False.""" | 553 | """Test that it skips GC if opt.auto_gc is False.""" |
| 554 | self.opt.auto_gc = False | 554 | self.opt.auto_gc = False |
| 555 | self.cmd._SetPreciousObjectsState = mock.Mock() | 555 | with mock.patch.object( |
| 556 | self.cmd._GCProjects([self.project], self.opt, None) | 556 | sync.Sync, "_SetPreciousObjectsState" |
| 557 | self.cmd._SetPreciousObjectsState.assert_called_once_with( | 557 | ) as mock_set_state: |
| 558 | self.project, self.opt | 558 | self.cmd._GCProjects([self.project], self.opt, None) |
| 559 | ) | 559 | mock_set_state.assert_called_once_with(self.project, self.opt) |
| 560 | self.assertFalse(self.project.bare_git.gc.called) | 560 | self.assertFalse(self.project.bare_git.gc.called) |
| 561 | 561 | ||
| 562 | @mock.patch("subcmds.sync.Progress") | 562 | @mock.patch("subcmds.sync.Progress") |
| 563 | def test_GCProjects_sequential(self, mock_progress): | 563 | def test_GCProjects_sequential(self, mock_progress): |
| 564 | """Test sequential GC (jobs < 2).""" | 564 | """Test sequential GC (jobs < 2).""" |
| 565 | self.cmd._SetPreciousObjectsState = mock.Mock() | 565 | with mock.patch.object(sync.Sync, "_SetPreciousObjectsState"): |
| 566 | self.cmd._GCProjects([self.project], self.opt, None) | 566 | self.cmd._GCProjects([self.project], self.opt, None) |
| 567 | self.project.config.SetString.assert_called_once_with( | 567 | self.project.bare_git.gc.assert_called_once_with( |
| 568 | "gc.autoDetach", "false" | 568 | "--auto", config={"gc.autoDetach": "false"} |
| 569 | ) | 569 | ) |
| 570 | self.project.bare_git.gc.assert_called_once_with("--auto") | 570 | # Verify that gc.autoDetach was not permanently set in config. |
| 571 | for call in self.project.config.SetString.call_args_list: | ||
| 572 | self.assertNotEqual(call.args[0], "gc.autoDetach") | ||
| 571 | 573 | ||
| 572 | @mock.patch("subcmds.sync.Progress") | 574 | @mock.patch("subcmds.sync.Progress") |
| 573 | def test_GCProjects_parallel(self, mock_progress): | 575 | def test_GCProjects_parallel(self, mock_progress): |
| 574 | """Test parallel GC (jobs >= 2).""" | 576 | """Test parallel GC (jobs >= 2).""" |
| 575 | self.opt.jobs = 2 | 577 | self.opt.jobs = 2 |
| 576 | self.cmd._SetPreciousObjectsState = mock.Mock() | 578 | with mock.patch.object(sync.Sync, "_SetPreciousObjectsState"): |
| 577 | 579 | with mock.patch("subcmds.sync._threading.Thread") as mock_thread: | |
| 578 | with mock.patch("subcmds.sync._threading.Thread") as mock_thread: | 580 | mock_t = mock.MagicMock() |
| 579 | mock_t = mock.MagicMock() | 581 | mock_thread.return_value = mock_t |
| 580 | mock_thread.return_value = mock_t | 582 | err_event = mock.Mock() |
| 581 | err_event = mock.Mock() | 583 | err_event.is_set.return_value = False |
| 582 | err_event.is_set.return_value = False | 584 | self.cmd._GCProjects([self.project], self.opt, err_event) |
| 583 | self.cmd._GCProjects([self.project], self.opt, err_event) | ||
| 584 | 585 | ||
| 585 | self.assertTrue(mock_thread.called) | 586 | self.assertTrue(mock_thread.called) |
| 586 | 587 | ||
