Add option to check status of projects in parallel.

Change-Id: I6ac653f88573def8bb3d96031d3570ff966251ad
diff --git a/project.py b/project.py
index 4cef856..9d98041 100644
--- a/project.py
+++ b/project.py
@@ -650,13 +650,18 @@
 
     return False
 
-  def PrintWorkTreeStatus(self):
+  def PrintWorkTreeStatus(self, output_redir=None):
     """Prints the status of the repository to stdout.
+
+    Args:
+      output: If specified, redirect the output to this object.
     """
     if not os.path.isdir(self.worktree):
-      print ''
-      print 'project %s/' % self.relpath
-      print '  missing (run "repo sync")'
+      if output_redir == None:
+        output_redir = sys.stdout
+      print >>output_redir, ''
+      print >>output_redir, 'project %s/' % self.relpath
+      print >>output_redir, '  missing (run "repo sync")'
       return
 
     self.work_git.update_index('-q',
@@ -671,6 +676,8 @@
       return 'CLEAN'
 
     out = StatusColoring(self.config)
+    if not output_redir == None:
+      out.redirect(output_redir)
     out.project('project %-40s', self.relpath + '/')
 
     branch = self.CurrentBranch
@@ -720,6 +727,7 @@
       else:
         out.write('%s', line)
       out.nl()
+
     return 'DIRTY'
 
   def PrintWorkTreeDiff(self):
diff --git a/subcmds/status.py b/subcmds/status.py
index b0d419a..041bcd0 100644
--- a/subcmds/status.py
+++ b/subcmds/status.py
@@ -15,6 +15,15 @@
 
 from command import PagedCommand
 
+try:
+  import threading as _threading
+except ImportError:
+  import dummy_threading as _threading
+
+import itertools
+import sys
+import StringIO
+
 class Status(PagedCommand):
   common = True
   helpSummary = "Show the working tree status"
@@ -27,6 +36,9 @@
 specified.  A summary is displayed, one line per file where there
 is a difference between these three states.
 
+The -j/--jobs option can be used to run multiple status queries
+in parallel.
+
 Status Display
 --------------
 
@@ -60,9 +72,34 @@
 
 """
 
+  def _Options(self, p):
+    p.add_option('-j', '--jobs',
+                 dest='jobs', action='store', type='int', default=2,
+                 help="number of projects to check simultaneously")
+
+  def _StatusHelper(self, project, clean_counter, sem, output):
+    """Obtains the status for a specific project.
+
+    Obtains the status for a project, redirecting the output to
+    the specified object. It will release the semaphore
+    when done.
+
+    Args:
+      project: Project to get status of.
+      clean_counter: Counter for clean projects.
+      sem: Semaphore, will call release() when complete.
+      output: Where to output the status.
+    """
+    try:
+      state = project.PrintWorkTreeStatus(output)
+      if state == 'CLEAN':
+        clean_counter.next()
+    finally:
+      sem.release()
+
   def Execute(self, opt, args):
     all = self.GetProjects(args)
-    clean = 0
+    counter = itertools.count()
 
     on = {}
     for project in all:
@@ -77,9 +114,24 @@
     for cb in branch_names:
       print '# on branch %s' % cb
 
-    for project in all:
-      state = project.PrintWorkTreeStatus()
-      if state == 'CLEAN':
-        clean += 1
-    if len(all) == clean:
+    if opt.jobs == 1:
+      for project in all:
+        state = project.PrintWorkTreeStatus()
+        if state == 'CLEAN':
+          counter.next()
+    else:
+      sem = _threading.Semaphore(opt.jobs)
+      threads_and_output = []
+      for project in all:
+        sem.acquire()
+        output = StringIO.StringIO()
+        t = _threading.Thread(target=self._StatusHelper,
+                              args=(project, counter, sem, output))
+        threads_and_output.append((t, output))
+        t.start()
+      for (t, output) in threads_and_output:
+        t.join()
+        sys.stdout.write(output.getvalue())
+        output.close()
+    if len(all) == counter.next():
       print 'nothing to commit (working directory clean)'