Add git-map and git-map-branches to depot_tools.

git-map: Show your local repo's history in a pseudo-graphical format from the command line.

git-map-branches: Show the topology of all of your branches, and their upstream relationships.

git-nav-upstream: Navigate (checkout) to the upstream branch of the current branch.

git-nav-downstream: Navigate (checkout) to a downstream branch of the current branch. If there's more than one downstream branch, then present a menu to select which one you want.

R=agable@chromium.org, hinoka@chromium.org, stip@chromium.org, szager@chromium.org
BUG=

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

git-svn-id: svn://svn.chromium.org/chrome/trunk/tools/depot_tools@256384 0039d316-1c4b-4281-b951-d872f2087c98
diff --git a/git_map_branches.py b/git_map_branches.py
new file mode 100755
index 0000000..5fde84b
--- /dev/null
+++ b/git_map_branches.py
@@ -0,0 +1,81 @@
+#!/usr/bin/env python
+"""
+Provides a short mapping of all the branches in your local repo, organized by
+their upstream ('tracking branch') layout. Example:
+
+origin/master
+  cool_feature
+    dependent_feature
+    other_dependent_feature
+  other_feature
+
+Branches are colorized as follows:
+  * Red - a remote branch (usually the root of all local branches)
+  * Cyan - a local branch which is the same as HEAD
+    * Note that multiple branches may be Cyan, if they are all on the same
+      commit, and you have that commit checked out.
+  * Green - a local branch
+  * Magenta - a placeholder for the '{NO UPSTREAM}' "branch". If you have
+    local branches which do not track any upstream, then you will see this.
+"""
+import collections
+import sys
+
+from third_party import colorama
+from third_party.colorama import Fore, Style
+
+from git_common import current_branch, branches, upstream, hash_one, hash_multi
+
+NO_UPSTREAM = '{NO UPSTREAM}'
+
+def print_branch(cur, cur_hash, branch, branch_hashes, par_map, branch_map,
+                 depth=0):
+  branch_hash = branch_hashes[branch]
+  if branch.startswith('origin'):
+    color = Fore.RED
+  elif branch == NO_UPSTREAM:
+    color = Fore.MAGENTA
+  elif branch_hash == cur_hash:
+    color = Fore.CYAN
+  else:
+    color = Fore.GREEN
+
+  if branch_hash == cur_hash:
+    color += Style.BRIGHT
+  else:
+    color += Style.NORMAL
+
+  print color + "  "*depth + branch + (" *" if branch == cur else "")
+  for child in par_map.pop(branch, ()):
+    print_branch(cur, cur_hash, child, branch_hashes, par_map, branch_map,
+                 depth=depth+1)
+
+
+def main(argv):
+  colorama.init()
+  assert len(argv) == 1, "No arguments expected"
+  branch_map = {}
+  par_map = collections.defaultdict(list)
+  for branch in branches():
+    par = upstream(branch) or NO_UPSTREAM
+    branch_map[branch] = par
+    par_map[par].append(branch)
+
+  current = current_branch()
+  hashes = hash_multi(current, *branch_map.keys())
+  current_hash = hashes[0]
+  par_hashes = {k: hashes[i+1] for i, k in enumerate(branch_map.iterkeys())}
+  par_hashes[NO_UPSTREAM] = 0
+  while par_map:
+    for parent in par_map:
+      if parent not in branch_map:
+        if parent not in par_hashes:
+          par_hashes[parent] = hash_one(parent)
+        print_branch(current, current_hash, parent, par_hashes, par_map,
+                     branch_map)
+        break
+
+
+if __name__ == '__main__':
+  sys.exit(main(sys.argv))
+