Add willis, a tool to show the status of your entire source tree.

Willis was inspired by Bill's wtf script.

Change-Id: Ie58c51bfafe1041bf7e9d6509b2acb050ba55ac7

BUG=None
TEST=run willis, see that there are changes in my tree, be happy

Review URL: http://codereview.chromium.org/6409029
diff --git a/host/willis b/host/willis
new file mode 100755
index 0000000..16e4312
--- /dev/null
+++ b/host/willis
@@ -0,0 +1,109 @@
+#!/usr/bin/env python
+# Copyright (c) 2011 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+"""Display active git branches and code changes in a ChromiumOS workspace."""
+
+import os
+import re
+import subprocess
+
+def RunCommand(path, command):
+  """Run a command in a given directory, return stdout."""
+
+  return subprocess.Popen(command,
+                          cwd=path,
+                          stdout=subprocess.PIPE).communicate()[0].rstrip()
+
+#
+# Taken with slight modification from gclient_utils.py in the depot_tools
+# project.
+#
+def FindFileUpwards(filename, path):
+  """Search upwards from the a directory to find a file."""
+
+  path = os.path.realpath(path)
+  while True:
+    file_path = os.path.join(path, filename)
+    if os.path.exists(file_path):
+      return file_path
+    (new_path, _) = os.path.split(path)
+    if new_path == path:
+      return None
+    path = new_path
+
+
+def ShowName(relative_name, color):
+  """Display the directory name."""
+
+  if color:
+    print('\033[44m\033[37m%s\033[0m' % relative_name)
+  else:
+    print relative_name
+
+
+def ShowDir(full_name, relative_name, color):
+  """Display active work in a single git repo."""
+
+  lines_printed = 0
+  command = ['git', 'branch', '-vv']
+
+  if color:
+    command.append('--color')
+
+  branch = RunCommand(full_name, command)
+  lines  = branch.splitlines()
+
+  if (len(lines) > 1 or
+      (len(lines) == 1 and not re.search(r"\(no branch\)", lines[0]))):
+    if lines_printed == 0:
+      ShowName(relative_name, color)
+    lines_printed += 1
+    print branch
+
+  status = RunCommand(full_name, ['git', 'status', '-s'])
+
+  if status.splitlines():
+    if lines_printed == 0:
+      ShowName(relative_name, color)
+    if lines_printed == 1:
+      print '---------------'
+    lines_printed += 1
+    print status
+
+  if lines_printed > 0:
+    print ""
+
+
+def FindRoot():
+  """Returns the repo root."""
+
+  repo_file = '.repo'
+  repo_path = FindFileUpwards(repo_file, os.getcwd())
+
+  if repo_path is None:
+    raise Exception('Failed to find %s.' % repo_file)
+
+  return os.path.dirname(repo_path)
+
+
+def main():
+  """Take no arguments."""
+
+  color = os.isatty(1)
+  base = os.path.basename(os.getcwd())
+  root = FindRoot()
+  repos = RunCommand(root, ['repo', 'forall', '-c', 'pwd']).splitlines()
+
+  # We want to use the full path for testing, but we want to use the relative
+  # path for display.
+  fulldirs = [os.path.normpath(os.path.join(root, p)) for p in repos]
+  reldirs = [re.sub('^' + re.escape(base), '.', p) for p in repos]
+
+  for full_path, relative_path in zip(fulldirs, reldirs):
+    ShowDir(full_path, relative_path, color)
+
+
+if __name__ == '__main__':
+  main()