sync: Improve UI and error reporting for interleaved mode

This fixes two issues:
1. the progress bar could show a count greater than the total if new projects were discovered mid-sync. Update the progress bar total dynamically
2. Make "Stall detected" error message more actionable

Bug: 432206932
Change-Id: Ie2a4ada5b1770cae0302fb06590641c522cbb7e7
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/491941
Tested-by: Gavin Mak <gavinmak@google.com>
Reviewed-by: Scott Lee <ddoman@google.com>
Commit-Queue: Gavin Mak <gavinmak@google.com>
diff --git a/progress.py b/progress.py
index 31a4890..30ec8c3 100644
--- a/progress.py
+++ b/progress.py
@@ -119,6 +119,11 @@
         if not quiet and show_elapsed:
             self._update_thread.start()
 
+    def update_total(self, new_total):
+        """Updates the total if the new total is larger."""
+        if new_total > self._total:
+            self._total = new_total
+
     def _update_loop(self):
         while True:
             self.update(inc=0)
diff --git a/subcmds/sync.py b/subcmds/sync.py
index 250925f..13a322b 100644
--- a/subcmds/sync.py
+++ b/subcmds/sync.py
@@ -2505,11 +2505,22 @@
 
                         pending_relpaths = {p.relpath for p in projects_to_sync}
                         if previously_pending_relpaths == pending_relpaths:
+                            stalled_projects_str = "\n".join(
+                                f" - {path}"
+                                for path in sorted(list(pending_relpaths))
+                            )
                             logger.error(
-                                "Stall detected in interleaved sync, not all "
-                                "projects could be synced."
+                                "The following projects failed and could not "
+                                "be synced:\n%s",
+                                stalled_projects_str,
                             )
                             err_event.set()
+
+                            # Include these in the final error report.
+                            self._interleaved_err_checkout = True
+                            self._interleaved_err_checkout_results.extend(
+                                list(pending_relpaths)
+                            )
                             break
                         previously_pending_relpaths = pending_relpaths
 
@@ -2570,6 +2581,7 @@
                             manifest=manifest,
                             all_manifests=not opt.this_manifest_only,
                         )
+                        pm.update_total(len(project_list))
                 finally:
                     sync_event.set()
                     sync_progress_thread.join()