sync: Share manifest list update logic between sync modes

Extract the manifest update loop from _SyncPhased into a new
_UpdateManifestLists method and use it in both sync types.

Bug: 421935613
Change-Id: If499a3ce4a0bbb3c4641dba52ca5c1c82b11f16f
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/484341
Reviewed-by: Scott Lee <ddoman@google.com>
Commit-Queue: Gavin Mak <gavinmak@google.com>
Tested-by: Gavin Mak <gavinmak@google.com>
diff --git a/subcmds/sync.py b/subcmds/sync.py
index b848d13..3d4ab75 100644
--- a/subcmds/sync.py
+++ b/subcmds/sync.py
@@ -26,7 +26,7 @@
 import sys
 import tempfile
 import time
-from typing import List, NamedTuple, Optional, Set, Union
+from typing import List, NamedTuple, Optional, Set, Tuple, Union
 import urllib.error
 import urllib.parse
 import urllib.request
@@ -2006,6 +2006,54 @@
 
         return _threading.Thread(target=_monitor_loop, daemon=True)
 
+    def _UpdateManifestLists(
+        self,
+        opt: optparse.Values,
+        err_event: multiprocessing.Event,
+        errors: List[Exception],
+    ) -> Tuple[bool, bool]:
+        """Updates project lists and copy/link files for all manifests.
+
+        Args:
+            opt: Program options from optparse.
+            err_event: An event to set if any error occurs.
+            errors: A list to append any encountered exceptions to.
+
+        Returns:
+            A tuple (err_update_projects, err_update_linkfiles) indicating
+                an error for each task.
+        """
+        err_update_projects = False
+        err_update_linkfiles = False
+        for m in self.ManifestList(opt):
+            if m.IsMirror or m.IsArchive:
+                continue
+
+            try:
+                self.UpdateProjectList(opt, m)
+            except Exception as e:
+                err_event.set()
+                err_update_projects = True
+                errors.append(e)
+                if isinstance(e, DeleteWorktreeError):
+                    errors.extend(e.aggregate_errors)
+                if opt.fail_fast:
+                    logger.error("error: Local checkouts *not* updated.")
+                    raise SyncFailFastError(aggregate_errors=errors)
+
+            try:
+                self.UpdateCopyLinkfileList(m)
+            except Exception as e:
+                err_event.set()
+                err_update_linkfiles = True
+                errors.append(e)
+                if opt.fail_fast:
+                    logger.error(
+                        "error: Local update copyfile or linkfile failed."
+                    )
+                    raise SyncFailFastError(aggregate_errors=errors)
+        return err_update_projects, err_update_linkfiles
+
     def _SyncPhased(
         self,
         opt,
@@ -2064,34 +2112,11 @@
                     )
                     raise SyncFailFastError(aggregate_errors=errors)
 
-        for m in self.ManifestList(opt):
-            if m.IsMirror or m.IsArchive:
-                # Bail out now, we have no working tree.
-                continue
-
-            try:
-                self.UpdateProjectList(opt, m)
-            except Exception as e:
-                err_event.set()
-                err_update_projects = True
-                errors.append(e)
-                if isinstance(e, DeleteWorktreeError):
-                    errors.extend(e.aggregate_errors)
-                if opt.fail_fast:
-                    logger.error("error: Local checkouts *not* updated.")
-                    raise SyncFailFastError(aggregate_errors=errors)
-
-            try:
-                self.UpdateCopyLinkfileList(m)
-            except Exception as e:
-                err_update_linkfiles = True
-                errors.append(e)
-                err_event.set()
-                if opt.fail_fast:
-                    logger.error(
-                        "error: Local update copyfile or linkfile failed."
-                    )
-                    raise SyncFailFastError(aggregate_errors=errors)
+        err_update_projects, err_update_linkfiles = self._UpdateManifestLists(
+            opt,
+            err_event,
+            errors,
+        )
 
         err_results = []
         # NB: We don't exit here because this is the last step.
@@ -2495,7 +2520,7 @@
 
         pm.end()
 
-        # TODO(b/421935613): Add the manifest loop block from PhasedSync.
+        self._UpdateManifestLists(opt, err_event, errors)
         if not self.outer_client.manifest.IsArchive:
             self._GCProjects(project_list, opt, err_event)