sync: report list of failing git trees

When repo sync fails because some git trees are not in clean state and
as such can not be rebased automatically, it is a pain to figure out
which trees are the culprits.

With this patch the list of offending trees is printed when repo sync
reports checkout errors.

TEST=ran 'repo sync' and observed the proper list of directories show
     up after the final error message

Bug: https://crbug.com/gerrit/11293
Change-Id: Icdf1a03e9014ecb184f331f513cc9a2efc7d11ed
Signed-off-by: Vadim Bendebury <vbendeb@google.com>
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/244053
Reviewed-by: Mike Frysinger <vapier@google.com>
diff --git a/subcmds/sync.py b/subcmds/sync.py
index 9b4a614..97da620 100644
--- a/subcmds/sync.py
+++ b/subcmds/sync.py
@@ -440,7 +440,7 @@
     finally:
       sem.release()
 
-  def _CheckoutOne(self, opt, project, lock, pm, err_event):
+  def _CheckoutOne(self, opt, project, lock, pm, err_event, err_results):
     """Checkout work tree for one project
 
     Args:
@@ -452,6 +452,8 @@
           lock held).
       err_event: We'll set this event in the case of an error (after printing
           out info about the error).
+      err_results: A list of strings, paths to git repos where checkout
+          failed.
 
     Returns:
       Whether the fetch was successful.
@@ -496,6 +498,8 @@
         raise
     finally:
       if did_lock:
+        if not success:
+          err_results.append(project.relpath)
         lock.release()
       finish = time.time()
       self.event_log.AddSync(project, event_log.TASK_SYNC_LOCAL,
@@ -528,6 +532,7 @@
     threads = set()
     sem = _threading.Semaphore(syncjobs)
     err_event = _threading.Event()
+    err_results = []
 
     for project in all_projects:
       # Check for any errors before running any more tasks.
@@ -542,7 +547,8 @@
                       project=project,
                       lock=lock,
                       pm=pm,
-                      err_event=err_event)
+                      err_event=err_event,
+                      err_results=err_results)
         if syncjobs > 1:
           t = _threading.Thread(target=self._CheckoutWorker,
                                 kwargs=kwargs)
@@ -560,6 +566,9 @@
     # If we saw an error, exit with code 1 so that other scripts can check.
     if err_event.isSet():
       print('\nerror: Exited sync due to checkout errors', file=sys.stderr)
+      if err_results:
+        print('Failing repos:\n%s' % '\n'.join(err_results),
+              file=sys.stderr)
       sys.exit(1)
 
   def _GCProjects(self, projects):