diff --git a/subcmds/sync.py b/subcmds/sync.py index b02fdd022..c0310c56c 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, - ) - 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 + 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, ) - 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 + 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 + ) + 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() # Consolidate all captured output. captured_parts = [] diff --git a/tests/test_subcmds_sync.py b/tests/test_subcmds_sync.py index 9cd19f104..5955e4048 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()