Give git map-branches extra information behind -v and -vv flags.

This CL adds information to the git map-branches command. Invoking 
it map-branches with -v, will show tracking status of branches and
invoking with -vv will additionally show the Rietveld URL and the
branch hash.

BUG=None

Review URL: https://codereview.chromium.org/509843002

git-svn-id: svn://svn.chromium.org/chrome/trunk/tools/depot_tools@291776 0039d316-1c4b-4281-b951-d872f2087c98
diff --git a/git_common.py b/git_common.py
index 64a385b..31a62b8 100644
--- a/git_common.py
+++ b/git_common.py
@@ -91,6 +91,9 @@
 GIT_TRANSIENT_ERRORS_RE = re.compile('|'.join(GIT_TRANSIENT_ERRORS),
                                      re.IGNORECASE)
 
+# First version where the for-each-ref command's format string supported the
+# upstream:track token.
+MIN_UPSTREAM_TRACK_GIT_VERSION = (1, 9)
 
 class BadCommitRefException(Exception):
   def __init__(self, refs):
@@ -436,8 +439,11 @@
   return run('rev-parse', *reflike).splitlines()
 
 
-def hash_one(reflike):
-  return run('rev-parse', reflike)
+def hash_one(reflike, short=False):
+  args = ['rev-parse', reflike]
+  if short:
+    args.insert(1, '--short')
+  return run(*args)
 
 
 def in_rebase():
@@ -716,3 +722,46 @@
                branch+'@{upstream}')
   except subprocess2.CalledProcessError:
     return None
+
+def get_git_version():
+  """Returns a tuple that contains the numeric components of the current git
+  version."""
+  version_string = run('--version')
+  version_match = re.search(r'(\d+.)+(\d+)', version_string)
+  version = version_match.group() if version_match else ''
+
+  return tuple(int(x) for x in version.split('.'))
+
+
+def get_all_tracking_info():
+  format_string = (
+      '--format=%(refname:short):%(objectname:short):%(upstream:short):')
+
+  # This is not covered by the depot_tools CQ which only has git version 1.8.
+  if get_git_version() >= MIN_UPSTREAM_TRACK_GIT_VERSION:  # pragma: no cover
+    format_string += '%(upstream:track)'
+
+  info_map = {}
+  data = run('for-each-ref', format_string, 'refs/heads')
+  TrackingInfo = collections.namedtuple(
+      'TrackingInfo', 'hash upstream ahead behind')
+  for line in data.splitlines():
+    (branch, branch_hash, upstream_branch, tracking_status) = line.split(':')
+
+    ahead_match = re.search(r'ahead (\d+)', tracking_status)
+    ahead = int(ahead_match.group(1)) if ahead_match else None
+
+    behind_match = re.search(r'behind (\d+)', tracking_status)
+    behind = int(behind_match.group(1)) if behind_match else None
+
+    info_map[branch] = TrackingInfo(
+        hash=branch_hash, upstream=upstream_branch, ahead=ahead, behind=behind)
+
+  # Set None for upstreams which are not branches (e.g empty upstream, remotes
+  # and deleted upstream branches).
+  missing_upstreams = {}
+  for info in info_map.values():
+    if info.upstream not in info_map and info.upstream not in missing_upstreams:
+      missing_upstreams[info.upstream] = None
+
+  return dict(info_map.items() + missing_upstreams.items())