From 720bd1e96b4e9c36d035987578fc01a9939d753f Mon Sep 17 00:00:00 2001 From: Gavin Mak Date: Wed, 23 Jul 2025 15:23:10 -0700 Subject: sync: Don't checkout if no worktree Interleaved sync should not try checkout out a project if it's a mirror. Change-Id: I2549faab197a3202d79a10e44b449b68d53e3fe7 Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/492942 Commit-Queue: Gavin Mak Reviewed-by: Scott Lee Tested-by: Gavin Mak --- subcmds/sync.py | 92 ++++++++++++++++++++++++---------------------- tests/test_subcmds_sync.py | 20 ++++++++++ 2 files changed, 69 insertions(+), 43 deletions(-) diff --git a/subcmds/sync.py b/subcmds/sync.py index b02fdd02..c0310c56 100644 --- a/subcmds/sync.py +++ b/subcmds/sync.py @@ -2269,51 +2269,57 @@ later is required to fix a server side protocol bug. checkout_finish = None checkout_stderr = "" - if fetch_success and not opt.network_only: - checkout_start = time.time() - stderr_capture = io.StringIO() - try: - with contextlib.redirect_stderr(stderr_capture): - syncbuf = SyncBuffer( - project.manifest.manifestProject.config, - detach_head=opt.detach_head, + if fetch_success: + # We skip checkout if it's network-only or if the project has no + # working tree (e.g., a mirror). + if opt.network_only or not project.worktree: + checkout_success = True + else: + # This is a normal project that needs a checkout. + checkout_start = time.time() + stderr_capture = io.StringIO() + try: + with contextlib.redirect_stderr(stderr_capture): + syncbuf = SyncBuffer( + project.manifest.manifestProject.config, + detach_head=opt.detach_head, + ) + local_half_errors = [] + project.Sync_LocalHalf( + syncbuf, + force_sync=opt.force_sync, + force_checkout=opt.force_checkout, + force_rebase=opt.rebase, + errors=local_half_errors, + verbose=opt.verbose, + ) + checkout_success = syncbuf.Finish() + if local_half_errors: + checkout_error = SyncError( + aggregate_errors=local_half_errors + ) + except KeyboardInterrupt: + logger.error( + "Keyboard interrupt while processing %s", project.name ) - local_half_errors = [] - project.Sync_LocalHalf( - syncbuf, - force_sync=opt.force_sync, - force_checkout=opt.force_checkout, - force_rebase=opt.rebase, - errors=local_half_errors, - verbose=opt.verbose, + except GitError as e: + checkout_error = e + logger.error( + "error.GitError: Cannot checkout %s: %s", + project.name, + e, ) - checkout_success = syncbuf.Finish() - if local_half_errors: - checkout_error = SyncError( - aggregate_errors=local_half_errors - ) - except KeyboardInterrupt: - logger.error( - "Keyboard interrupt while processing %s", project.name - ) - except GitError as e: - checkout_error = e - logger.error( - "error.GitError: Cannot checkout %s: %s", project.name, e - ) - except Exception as e: - checkout_error = e - logger.error( - "error: Cannot checkout %s: %s: %s", - project.name, - type(e).__name__, - e, - ) - finally: - checkout_finish = time.time() - checkout_stderr = stderr_capture.getvalue() - elif fetch_success: - checkout_success = True + except Exception as e: + checkout_error = e + logger.error( + "error: Cannot checkout %s: %s: %s", + project.name, + type(e).__name__, + e, + ) + finally: + checkout_finish = time.time() + checkout_stderr = stderr_capture.getvalue() # Consolidate all captured output. captured_parts = [] diff --git a/tests/test_subcmds_sync.py b/tests/test_subcmds_sync.py index 9cd19f10..5955e404 100644 --- a/tests/test_subcmds_sync.py +++ b/tests/test_subcmds_sync.py @@ -309,6 +309,7 @@ class FakeProject: self.relpath = relpath self.name = name or relpath self.objdir = objdir or relpath + self.worktree = relpath self.use_git_worktrees = False self.UseAlternates = False @@ -836,6 +837,25 @@ class InterleavedSyncTest(unittest.TestCase): project.Sync_NetworkHalf.assert_called_once() project.Sync_LocalHalf.assert_not_called() + def test_worker_no_worktree(self): + """Test interleaved sync does not checkout with no worktree.""" + opt = self._get_opts() + project = self.projA + project.worktree = None + project.Sync_NetworkHalf = mock.Mock( + return_value=SyncNetworkHalfResult(error=None, remote_fetched=True) + ) + project.Sync_LocalHalf = mock.Mock() + self.mock_context["projects"] = [project] + + result_obj = self.cmd._SyncProjectList(opt, [0]) + result = result_obj.results[0] + + self.assertTrue(result.fetch_success) + self.assertTrue(result.checkout_success) + project.Sync_NetworkHalf.assert_called_once() + project.Sync_LocalHalf.assert_not_called() + def test_worker_fetch_fails_exception(self): """Test _SyncProjectList with an exception during fetch.""" opt = self._get_opts() -- cgit v1.2.3-54-g00ecf