Commit ffde55c5 authored by calamity@chromium.org's avatar calamity@chromium.org

Make git-map-branches -vvv show CL status colors.

This CL makes git-map-branches show CL status colors like git cl status
when -vvv is used. Statuses are fetched in parallel for speed.

BUG=379849

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

git-svn-id: svn://svn.chromium.org/chrome/trunk/tools/depot_tools@294414 0039d316-1c4b-4281-b951-d872f2087c98
parent dfaffbe3
...@@ -8,6 +8,7 @@ ...@@ -8,6 +8,7 @@
"""A git-command for integrating reviews on Rietveld.""" """A git-command for integrating reviews on Rietveld."""
from distutils.version import LooseVersion from distutils.version import LooseVersion
from multiprocessing.pool import ThreadPool
import base64 import base64
import glob import glob
import json import json
...@@ -20,7 +21,6 @@ import stat ...@@ -20,7 +21,6 @@ import stat
import sys import sys
import tempfile import tempfile
import textwrap import textwrap
import threading
import urllib2 import urllib2
import urlparse import urlparse
import webbrowser import webbrowser
...@@ -1342,6 +1342,51 @@ def color_for_status(status): ...@@ -1342,6 +1342,51 @@ def color_for_status(status):
'error': Fore.WHITE, 'error': Fore.WHITE,
}.get(status, Fore.WHITE) }.get(status, Fore.WHITE)
def fetch_cl_status(b):
"""Fetches information for an issue and returns (branch, issue, color)."""
c = Changelist(branchref=b)
i = c.GetIssueURL()
status = c.GetStatus()
color = color_for_status(status)
if i and (not status or status == 'error'):
# The issue probably doesn't exist anymore.
i += ' (broken)'
return (b, i, color)
def get_cl_statuses(branches, fine_grained, max_processes=None):
"""Returns a blocking iterable of (branch, issue, color) for given branches.
If fine_grained is true, this will fetch CL statuses from the server.
Otherwise, simply indicate if there's a matching url for the given branches.
If max_processes is specified, it is used as the maximum number of processes
to spawn to fetch CL status from the server. Otherwise 1 process per branch is
spawned.
"""
# Silence upload.py otherwise it becomes unwieldly.
upload.verbosity = 0
if fine_grained:
# Process one branch synchronously to work through authentication, then
# spawn processes to process all the other branches in parallel.
if branches:
yield fetch_cl_status(branches[0])
branches_to_fetch = branches[1:]
pool = ThreadPool(
min(max_processes, len(branches_to_fetch))
if max_processes is not None
else len(branches_to_fetch))
for x in pool.imap_unordered(fetch_cl_status, branches_to_fetch):
yield x
else:
# Do not use GetApprovingReviewers(), since it requires an HTTP request.
for b in branches:
c = Changelist(branchref=b)
url = c.GetIssueURL()
yield (b, url, Fore.BLUE if url else Fore.WHITE)
def CMDstatus(parser, args): def CMDstatus(parser, args):
"""Show status of changelists. """Show status of changelists.
...@@ -1360,6 +1405,9 @@ def CMDstatus(parser, args): ...@@ -1360,6 +1405,9 @@ def CMDstatus(parser, args):
help='print only specific field (desc|id|patch|url)') help='print only specific field (desc|id|patch|url)')
parser.add_option('-f', '--fast', action='store_true', parser.add_option('-f', '--fast', action='store_true',
help='Do not retrieve review status') help='Do not retrieve review status')
parser.add_option(
'-j', '--maxjobs', action='store', type=int,
help='The maximum number of jobs to use when retrieving review status')
(options, args) = parser.parse_args(args) (options, args) = parser.parse_args(args)
if args: if args:
parser.error('Unsupported args: %s' % args) parser.error('Unsupported args: %s' % args)
...@@ -1391,49 +1439,17 @@ def CMDstatus(parser, args): ...@@ -1391,49 +1439,17 @@ def CMDstatus(parser, args):
branches = [c.GetBranch() for c in changes] branches = [c.GetBranch() for c in changes]
alignment = max(5, max(len(b) for b in branches)) alignment = max(5, max(len(b) for b in branches))
print 'Branches associated with reviews:' print 'Branches associated with reviews:'
# Adhoc thread pool to request data concurrently. output = get_cl_statuses(branches,
output = Queue.Queue() fine_grained=not options.fast,
max_processes=options.maxjobs)
# Silence upload.py otherwise it becomes unweldly.
upload.verbosity = 0
if not options.fast:
def fetch(b):
"""Fetches information for an issue and returns (branch, issue, color)."""
c = Changelist(branchref=b)
i = c.GetIssueURL()
status = c.GetStatus()
color = color_for_status(status)
if i and (not status or status == 'error'):
# The issue probably doesn't exist anymore.
i += ' (broken)'
output.put((b, i, color))
# Process one branch synchronously to work through authentication, then
# spawn threads to process all the other branches in parallel.
if branches:
fetch(branches[0])
threads = [
threading.Thread(target=fetch, args=(b,)) for b in branches[1:]]
for t in threads:
t.daemon = True
t.start()
else:
# Do not use GetApprovingReviewers(), since it requires an HTTP request.
for b in branches:
c = Changelist(branchref=b)
url = c.GetIssueURL()
output.put((b, url, Fore.BLUE if url else Fore.WHITE))
tmp = {} branch_statuses = {}
alignment = max(5, max(len(ShortBranchName(b)) for b in branches)) alignment = max(5, max(len(ShortBranchName(b)) for b in branches))
for branch in sorted(branches): for branch in sorted(branches):
while branch not in tmp: while branch not in branch_statuses:
b, i, color = output.get() b, i, color = output.next()
tmp[b] = (i, color) branch_statuses[b] = (i, color)
issue, color = tmp.pop(branch) issue, color = branch_statuses.pop(branch)
reset = Fore.RESET reset = Fore.RESET
if not sys.stdout.isatty(): if not sys.stdout.isatty():
color = '' color = ''
......
...@@ -109,6 +109,7 @@ class BranchMapper(object): ...@@ -109,6 +109,7 @@ class BranchMapper(object):
def __init__(self): def __init__(self):
self.verbosity = 0 self.verbosity = 0
self.maxjobs = 0
self.output = OutputManager() self.output = OutputManager()
self.__gone_branches = set() self.__gone_branches = set()
self.__branches_info = None self.__branches_info = None
...@@ -116,10 +117,25 @@ class BranchMapper(object): ...@@ -116,10 +117,25 @@ class BranchMapper(object):
self.__current_branch = None self.__current_branch = None
self.__current_hash = None self.__current_hash = None
self.__tag_set = None self.__tag_set = None
self.__status_info = {}
def start(self): def start(self):
self.__branches_info = get_branches_info( self.__branches_info = get_branches_info(
include_tracking_status=self.verbosity >= 1) include_tracking_status=self.verbosity >= 1)
if (self.verbosity >= 2):
# Avoid heavy import unless necessary.
from git_cl import get_cl_statuses
status_info = get_cl_statuses(self.__branches_info.keys(),
fine_grained=self.verbosity > 2,
max_processes=self.maxjobs)
for _ in xrange(len(self.__branches_info)):
# This is a blocking get which waits for the remote CL status to be
# retrieved.
(branch, url, color) = status_info.next()
self.__status_info[branch] = (url, color);
roots = set() roots = set()
# A map of parents to a list of their children. # A map of parents to a list of their children.
...@@ -238,11 +254,9 @@ class BranchMapper(object): ...@@ -238,11 +254,9 @@ class BranchMapper(object):
# The Rietveld issue associated with the branch. # The Rietveld issue associated with the branch.
if self.verbosity >= 2: if self.verbosity >= 2:
import git_cl # avoid heavy import cost unless we need it
none_text = '' if self.__is_invalid_parent(branch) else 'None' none_text = '' if self.__is_invalid_parent(branch) else 'None'
url = git_cl.Changelist( (url, color) = self.__status_info[branch]
branchref=branch).GetIssueURL() if branch_hash else None line.append(url or none_text, color=color)
line.append(url or none_text, color=Fore.BLUE if url else Fore.WHITE)
self.output.append(line) self.output.append(line)
...@@ -265,12 +279,16 @@ def main(argv): ...@@ -265,12 +279,16 @@ def main(argv):
help='Display branch hash and Rietveld URL') help='Display branch hash and Rietveld URL')
parser.add_argument('--no-color', action='store_true', dest='nocolor', parser.add_argument('--no-color', action='store_true', dest='nocolor',
help='Turn off colors.') help='Turn off colors.')
parser.add_argument(
'-j', '--maxjobs', action='store', type=int,
help='The number of jobs to use when retrieving review status')
opts = parser.parse_args(argv) opts = parser.parse_args(argv)
mapper = BranchMapper() mapper = BranchMapper()
mapper.verbosity = opts.v mapper.verbosity = opts.v
mapper.output.nocolor = opts.nocolor mapper.output.nocolor = opts.nocolor
mapper.maxjobs = opts.maxjobs
mapper.start() mapper.start()
print mapper.output.as_formatted_string() print mapper.output.as_formatted_string()
return 0 return 0
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment