| OLD | NEW |
| 1 #!/usr/bin/env python | 1 #!/usr/bin/env python |
| 2 # Copyright (c) 2012 The Chromium Authors. All rights reserved. | 2 # Copyright (c) 2012 The Chromium Authors. All rights reserved. |
| 3 # Use of this source code is governed by a BSD-style license that can be | 3 # Use of this source code is governed by a BSD-style license that can be |
| 4 # found in the LICENSE file. | 4 # found in the LICENSE file. |
| 5 | 5 |
| 6 # Copyright (C) 2008 Evan Martin <martine@danga.com> | 6 # Copyright (C) 2008 Evan Martin <martine@danga.com> |
| 7 | 7 |
| 8 """A git-command for integrating reviews on Rietveld.""" | 8 """A git-command for integrating reviews on Rietveld.""" |
| 9 | 9 |
| 10 from distutils.version import LooseVersion | 10 from distutils.version import LooseVersion |
| 11 from multiprocessing.pool import ThreadPool |
| 11 import base64 | 12 import base64 |
| 12 import glob | 13 import glob |
| 13 import json | 14 import json |
| 14 import logging | 15 import logging |
| 15 import optparse | 16 import optparse |
| 16 import os | 17 import os |
| 17 import Queue | 18 import Queue |
| 18 import re | 19 import re |
| 19 import stat | 20 import stat |
| 20 import sys | 21 import sys |
| 21 import tempfile | 22 import tempfile |
| 22 import textwrap | 23 import textwrap |
| 23 import threading | |
| 24 import urllib2 | 24 import urllib2 |
| 25 import urlparse | 25 import urlparse |
| 26 import webbrowser | 26 import webbrowser |
| 27 import zlib | 27 import zlib |
| 28 | 28 |
| 29 try: | 29 try: |
| 30 import readline # pylint: disable=F0401,W0611 | 30 import readline # pylint: disable=F0401,W0611 |
| 31 except ImportError: | 31 except ImportError: |
| 32 pass | 32 pass |
| 33 | 33 |
| (...skipping 1301 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 1335 return { | 1335 return { |
| 1336 'unsent': Fore.RED, | 1336 'unsent': Fore.RED, |
| 1337 'waiting': Fore.BLUE, | 1337 'waiting': Fore.BLUE, |
| 1338 'reply': Fore.YELLOW, | 1338 'reply': Fore.YELLOW, |
| 1339 'lgtm': Fore.GREEN, | 1339 'lgtm': Fore.GREEN, |
| 1340 'commit': Fore.MAGENTA, | 1340 'commit': Fore.MAGENTA, |
| 1341 'closed': Fore.CYAN, | 1341 'closed': Fore.CYAN, |
| 1342 'error': Fore.WHITE, | 1342 'error': Fore.WHITE, |
| 1343 }.get(status, Fore.WHITE) | 1343 }.get(status, Fore.WHITE) |
| 1344 | 1344 |
| 1345 def fetch_cl_status(b): |
| 1346 """Fetches information for an issue and returns (branch, issue, color).""" |
| 1347 c = Changelist(branchref=b) |
| 1348 i = c.GetIssueURL() |
| 1349 status = c.GetStatus() |
| 1350 color = color_for_status(status) |
| 1351 |
| 1352 if i and (not status or status == 'error'): |
| 1353 # The issue probably doesn't exist anymore. |
| 1354 i += ' (broken)' |
| 1355 |
| 1356 return (b, i, color) |
| 1357 |
| 1358 def get_cl_statuses(branches, fine_grained, max_processes=None): |
| 1359 """Returns a blocking iterable of (branch, issue, color) for given branches. |
| 1360 |
| 1361 If fine_grained is true, this will fetch CL statuses from the server. |
| 1362 Otherwise, simply indicate if there's a matching url for the given branches. |
| 1363 |
| 1364 If max_processes is specified, it is used as the maximum number of processes |
| 1365 to spawn to fetch CL status from the server. Otherwise 1 process per branch is |
| 1366 spawned. |
| 1367 """ |
| 1368 # Silence upload.py otherwise it becomes unwieldly. |
| 1369 upload.verbosity = 0 |
| 1370 |
| 1371 if fine_grained: |
| 1372 # Process one branch synchronously to work through authentication, then |
| 1373 # spawn processes to process all the other branches in parallel. |
| 1374 if branches: |
| 1375 yield fetch_cl_status(branches[0]) |
| 1376 |
| 1377 branches_to_fetch = branches[1:] |
| 1378 pool = ThreadPool( |
| 1379 min(max_processes, len(branches_to_fetch)) |
| 1380 if max_processes is not None |
| 1381 else len(branches_to_fetch)) |
| 1382 for x in pool.imap_unordered(fetch_cl_status, branches_to_fetch): |
| 1383 yield x |
| 1384 else: |
| 1385 # Do not use GetApprovingReviewers(), since it requires an HTTP request. |
| 1386 for b in branches: |
| 1387 c = Changelist(branchref=b) |
| 1388 url = c.GetIssueURL() |
| 1389 yield (b, url, Fore.BLUE if url else Fore.WHITE) |
| 1345 | 1390 |
| 1346 def CMDstatus(parser, args): | 1391 def CMDstatus(parser, args): |
| 1347 """Show status of changelists. | 1392 """Show status of changelists. |
| 1348 | 1393 |
| 1349 Colors are used to tell the state of the CL unless --fast is used: | 1394 Colors are used to tell the state of the CL unless --fast is used: |
| 1350 - Red not sent for review or broken | 1395 - Red not sent for review or broken |
| 1351 - Blue waiting for review | 1396 - Blue waiting for review |
| 1352 - Yellow waiting for you to reply to review | 1397 - Yellow waiting for you to reply to review |
| 1353 - Green LGTM'ed | 1398 - Green LGTM'ed |
| 1354 - Magenta in the commit queue | 1399 - Magenta in the commit queue |
| 1355 - Cyan was committed, branch can be deleted | 1400 - Cyan was committed, branch can be deleted |
| 1356 | 1401 |
| 1357 Also see 'git cl comments'. | 1402 Also see 'git cl comments'. |
| 1358 """ | 1403 """ |
| 1359 parser.add_option('--field', | 1404 parser.add_option('--field', |
| 1360 help='print only specific field (desc|id|patch|url)') | 1405 help='print only specific field (desc|id|patch|url)') |
| 1361 parser.add_option('-f', '--fast', action='store_true', | 1406 parser.add_option('-f', '--fast', action='store_true', |
| 1362 help='Do not retrieve review status') | 1407 help='Do not retrieve review status') |
| 1408 parser.add_option( |
| 1409 '-j', '--maxjobs', action='store', type=int, |
| 1410 help='The maximum number of jobs to use when retrieving review status') |
| 1363 (options, args) = parser.parse_args(args) | 1411 (options, args) = parser.parse_args(args) |
| 1364 if args: | 1412 if args: |
| 1365 parser.error('Unsupported args: %s' % args) | 1413 parser.error('Unsupported args: %s' % args) |
| 1366 | 1414 |
| 1367 if options.field: | 1415 if options.field: |
| 1368 cl = Changelist() | 1416 cl = Changelist() |
| 1369 if options.field.startswith('desc'): | 1417 if options.field.startswith('desc'): |
| 1370 print cl.GetDescription() | 1418 print cl.GetDescription() |
| 1371 elif options.field == 'id': | 1419 elif options.field == 'id': |
| 1372 issueid = cl.GetIssue() | 1420 issueid = cl.GetIssue() |
| (...skipping 11 matching lines...) Expand all Loading... |
| 1384 | 1432 |
| 1385 branches = RunGit(['for-each-ref', '--format=%(refname)', 'refs/heads']) | 1433 branches = RunGit(['for-each-ref', '--format=%(refname)', 'refs/heads']) |
| 1386 if not branches: | 1434 if not branches: |
| 1387 print('No local branch found.') | 1435 print('No local branch found.') |
| 1388 return 0 | 1436 return 0 |
| 1389 | 1437 |
| 1390 changes = (Changelist(branchref=b) for b in branches.splitlines()) | 1438 changes = (Changelist(branchref=b) for b in branches.splitlines()) |
| 1391 branches = [c.GetBranch() for c in changes] | 1439 branches = [c.GetBranch() for c in changes] |
| 1392 alignment = max(5, max(len(b) for b in branches)) | 1440 alignment = max(5, max(len(b) for b in branches)) |
| 1393 print 'Branches associated with reviews:' | 1441 print 'Branches associated with reviews:' |
| 1394 # Adhoc thread pool to request data concurrently. | 1442 output = get_cl_statuses(branches, |
| 1395 output = Queue.Queue() | 1443 fine_grained=not options.fast, |
| 1444 max_processes=options.maxjobs) |
| 1396 | 1445 |
| 1397 # Silence upload.py otherwise it becomes unweldly. | 1446 branch_statuses = {} |
| 1398 upload.verbosity = 0 | |
| 1399 | |
| 1400 if not options.fast: | |
| 1401 def fetch(b): | |
| 1402 """Fetches information for an issue and returns (branch, issue, color).""" | |
| 1403 c = Changelist(branchref=b) | |
| 1404 i = c.GetIssueURL() | |
| 1405 status = c.GetStatus() | |
| 1406 color = color_for_status(status) | |
| 1407 | |
| 1408 if i and (not status or status == 'error'): | |
| 1409 # The issue probably doesn't exist anymore. | |
| 1410 i += ' (broken)' | |
| 1411 | |
| 1412 output.put((b, i, color)) | |
| 1413 | |
| 1414 # Process one branch synchronously to work through authentication, then | |
| 1415 # spawn threads to process all the other branches in parallel. | |
| 1416 if branches: | |
| 1417 fetch(branches[0]) | |
| 1418 threads = [ | |
| 1419 threading.Thread(target=fetch, args=(b,)) for b in branches[1:]] | |
| 1420 for t in threads: | |
| 1421 t.daemon = True | |
| 1422 t.start() | |
| 1423 else: | |
| 1424 # Do not use GetApprovingReviewers(), since it requires an HTTP request. | |
| 1425 for b in branches: | |
| 1426 c = Changelist(branchref=b) | |
| 1427 url = c.GetIssueURL() | |
| 1428 output.put((b, url, Fore.BLUE if url else Fore.WHITE)) | |
| 1429 | |
| 1430 tmp = {} | |
| 1431 alignment = max(5, max(len(ShortBranchName(b)) for b in branches)) | 1447 alignment = max(5, max(len(ShortBranchName(b)) for b in branches)) |
| 1432 for branch in sorted(branches): | 1448 for branch in sorted(branches): |
| 1433 while branch not in tmp: | 1449 while branch not in branch_statuses: |
| 1434 b, i, color = output.get() | 1450 b, i, color = output.next() |
| 1435 tmp[b] = (i, color) | 1451 branch_statuses[b] = (i, color) |
| 1436 issue, color = tmp.pop(branch) | 1452 issue, color = branch_statuses.pop(branch) |
| 1437 reset = Fore.RESET | 1453 reset = Fore.RESET |
| 1438 if not sys.stdout.isatty(): | 1454 if not sys.stdout.isatty(): |
| 1439 color = '' | 1455 color = '' |
| 1440 reset = '' | 1456 reset = '' |
| 1441 print ' %*s : %s%s%s' % ( | 1457 print ' %*s : %s%s%s' % ( |
| 1442 alignment, ShortBranchName(branch), color, issue, reset) | 1458 alignment, ShortBranchName(branch), color, issue, reset) |
| 1443 | 1459 |
| 1444 cl = Changelist() | 1460 cl = Changelist() |
| 1445 print | 1461 print |
| 1446 print 'Current branch:', | 1462 print 'Current branch:', |
| (...skipping 1671 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 3118 if __name__ == '__main__': | 3134 if __name__ == '__main__': |
| 3119 # These affect sys.stdout so do it outside of main() to simplify mocks in | 3135 # These affect sys.stdout so do it outside of main() to simplify mocks in |
| 3120 # unit testing. | 3136 # unit testing. |
| 3121 fix_encoding.fix_encoding() | 3137 fix_encoding.fix_encoding() |
| 3122 colorama.init() | 3138 colorama.init() |
| 3123 try: | 3139 try: |
| 3124 sys.exit(main(sys.argv[1:])) | 3140 sys.exit(main(sys.argv[1:])) |
| 3125 except KeyboardInterrupt: | 3141 except KeyboardInterrupt: |
| 3126 sys.stderr.write('interrupted\n') | 3142 sys.stderr.write('interrupted\n') |
| 3127 sys.exit(1) | 3143 sys.exit(1) |
| OLD | NEW |