Chromium Code Reviews| 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 import difflib | 10 import difflib |
| 11 from distutils.version import LooseVersion | 11 from distutils.version import LooseVersion |
| 12 import json | 12 import json |
| 13 import logging | 13 import logging |
| 14 import optparse | 14 import optparse |
| 15 import os | 15 import os |
| 16 import Queue | |
| 16 import re | 17 import re |
| 17 import stat | 18 import stat |
| 18 import sys | 19 import sys |
| 19 import textwrap | 20 import textwrap |
| 21 import threading | |
| 20 import urllib2 | 22 import urllib2 |
| 21 import urlparse | 23 import urlparse |
| 22 | 24 |
| 23 try: | 25 try: |
| 24 import readline # pylint: disable=F0401,W0611 | 26 import readline # pylint: disable=F0401,W0611 |
| 25 except ImportError: | 27 except ImportError: |
| 26 pass | 28 pass |
| 27 | 29 |
| 28 | 30 |
| 29 from third_party import colorama | 31 from third_party import colorama |
| (...skipping 399 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 429 self.has_issue = False | 431 self.has_issue = False |
| 430 self.issue = None | 432 self.issue = None |
| 431 self.has_description = False | 433 self.has_description = False |
| 432 self.description = None | 434 self.description = None |
| 433 self.has_patchset = False | 435 self.has_patchset = False |
| 434 self.patchset = None | 436 self.patchset = None |
| 435 self._rpc_server = None | 437 self._rpc_server = None |
| 436 self.cc = None | 438 self.cc = None |
| 437 self.watchers = () | 439 self.watchers = () |
| 438 self._remote = None | 440 self._remote = None |
| 441 self._props = None | |
| 439 | 442 |
| 440 def GetCCList(self): | 443 def GetCCList(self): |
| 441 """Return the users cc'd on this CL. | 444 """Return the users cc'd on this CL. |
| 442 | 445 |
| 443 Return is a string suitable for passing to gcl with the --cc flag. | 446 Return is a string suitable for passing to gcl with the --cc flag. |
| 444 """ | 447 """ |
| 445 if self.cc is None: | 448 if self.cc is None: |
| 446 base_cc = settings .GetDefaultCCList() | 449 base_cc = settings .GetDefaultCCList() |
| 447 more_cc = ','.join(self.watchers) | 450 more_cc = ','.join(self.watchers) |
| 448 self.cc = ','.join(filter(None, (base_cc, more_cc))) or '' | 451 self.cc = ','.join(filter(None, (base_cc, more_cc))) or '' |
| (...skipping 219 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 668 | 671 |
| 669 def SetPatchset(self, patchset): | 672 def SetPatchset(self, patchset): |
| 670 """Set this branch's patchset. If patchset=0, clears the patchset.""" | 673 """Set this branch's patchset. If patchset=0, clears the patchset.""" |
| 671 if patchset: | 674 if patchset: |
| 672 RunGit(['config', self._PatchsetSetting(), str(patchset)]) | 675 RunGit(['config', self._PatchsetSetting(), str(patchset)]) |
| 673 else: | 676 else: |
| 674 RunGit(['config', '--unset', self._PatchsetSetting()], | 677 RunGit(['config', '--unset', self._PatchsetSetting()], |
| 675 stderr=subprocess2.PIPE, error_ok=True) | 678 stderr=subprocess2.PIPE, error_ok=True) |
| 676 self.has_patchset = False | 679 self.has_patchset = False |
| 677 | 680 |
| 678 def GetMostRecentPatchset(self, issue): | 681 def GetMostRecentPatchset(self, issue=None): |
| 679 return self.RpcServer().get_issue_properties( | 682 return self.GetIssueProperties(issue)['patchsets'][-1] |
| 680 int(issue), False)['patchsets'][-1] | |
| 681 | 683 |
| 682 def GetPatchSetDiff(self, issue, patchset): | 684 def GetPatchSetDiff(self, issue, patchset): |
| 683 return self.RpcServer().get( | 685 return self.RpcServer().get( |
| 684 '/download/issue%s_%s.diff' % (issue, patchset)) | 686 '/download/issue%s_%s.diff' % (issue, patchset)) |
| 685 | 687 |
| 688 def GetIssueProperties(self, issue=None): | |
| 689 if issue: | |
|
iannucci
2013/07/23 18:34:16
probably should do issue is None... technically 0
M-A Ruel
2013/07/23 18:49:43
Deleted this code.
| |
| 690 # Ignore self._props if issue is specified. | |
| 691 return self.RpcServer().get_issue_properties(issue, True) | |
| 692 | |
| 693 if self._props is None: | |
| 694 issue = self.GetIssue() | |
| 695 if not issue: | |
| 696 self._props = {} | |
| 697 else: | |
| 698 self._props = self.RpcServer().get_issue_properties(issue, True) | |
|
iannucci
2013/07/23 18:34:16
Hm, this function is a quite weird... Can't we jus
M-A Ruel
2013/07/23 18:49:43
Fixed.
| |
| 699 return self._props | |
| 700 | |
| 686 def GetApprovingReviewers(self): | 701 def GetApprovingReviewers(self): |
| 687 return get_approving_reviewers( | 702 return get_approving_reviewers(self.GetIssueProperties()) |
| 688 self.RpcServer().get_issue_properties(self.GetIssue(), True)) | |
| 689 | 703 |
| 690 def SetIssue(self, issue): | 704 def SetIssue(self, issue): |
| 691 """Set this branch's issue. If issue=0, clears the issue.""" | 705 """Set this branch's issue. If issue=0, clears the issue.""" |
| 692 if issue: | 706 if issue: |
| 693 RunGit(['config', self._IssueSetting(), str(issue)]) | 707 RunGit(['config', self._IssueSetting(), str(issue)]) |
| 694 if self.rietveld_server: | 708 if self.rietveld_server: |
| 695 RunGit(['config', self._RietveldServer(), self.rietveld_server]) | 709 RunGit(['config', self._RietveldServer(), self.rietveld_server]) |
| 696 else: | 710 else: |
| 697 RunGit(['config', '--unset', self._IssueSetting()]) | 711 RunGit(['config', '--unset', self._IssueSetting()]) |
| 698 self.SetPatchset(0) | 712 self.SetPatchset(0) |
| (...skipping 358 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 1057 else: | 1071 else: |
| 1058 print("Setting base-url to %s" % args[0]) | 1072 print("Setting base-url to %s" % args[0]) |
| 1059 return RunGit(['config', 'branch.%s.base-url' % branch, args[0]], | 1073 return RunGit(['config', 'branch.%s.base-url' % branch, args[0]], |
| 1060 error_ok=False).strip() | 1074 error_ok=False).strip() |
| 1061 | 1075 |
| 1062 | 1076 |
| 1063 def CMDstatus(parser, args): | 1077 def CMDstatus(parser, args): |
| 1064 """show status of changelists""" | 1078 """show status of changelists""" |
| 1065 parser.add_option('--field', | 1079 parser.add_option('--field', |
| 1066 help='print only specific field (desc|id|patch|url)') | 1080 help='print only specific field (desc|id|patch|url)') |
| 1081 parser.add_option('-f', '--fast', action='store_true', | |
| 1082 help='Do not retrieve review status') | |
| 1067 (options, args) = parser.parse_args(args) | 1083 (options, args) = parser.parse_args(args) |
| 1068 | 1084 |
| 1069 if options.field: | 1085 if options.field: |
| 1070 cl = Changelist() | 1086 cl = Changelist() |
| 1071 if options.field.startswith('desc'): | 1087 if options.field.startswith('desc'): |
| 1072 print cl.GetDescription() | 1088 print cl.GetDescription() |
| 1073 elif options.field == 'id': | 1089 elif options.field == 'id': |
| 1074 issueid = cl.GetIssue() | 1090 issueid = cl.GetIssue() |
| 1075 if issueid: | 1091 if issueid: |
| 1076 print issueid | 1092 print issueid |
| 1077 elif options.field == 'patch': | 1093 elif options.field == 'patch': |
| 1078 patchset = cl.GetPatchset() | 1094 patchset = cl.GetPatchset() |
| 1079 if patchset: | 1095 if patchset: |
| 1080 print patchset | 1096 print patchset |
| 1081 elif options.field == 'url': | 1097 elif options.field == 'url': |
| 1082 url = cl.GetIssueURL() | 1098 url = cl.GetIssueURL() |
| 1083 if url: | 1099 if url: |
| 1084 print url | 1100 print url |
| 1085 return 0 | 1101 return 0 |
| 1086 | 1102 |
| 1087 branches = RunGit(['for-each-ref', '--format=%(refname)', 'refs/heads']) | 1103 branches = RunGit(['for-each-ref', '--format=%(refname)', 'refs/heads']) |
| 1088 if not branches: | 1104 if not branches: |
| 1089 print('No local branch found.') | 1105 print('No local branch found.') |
| 1090 return 0 | 1106 return 0 |
| 1091 | 1107 |
| 1092 changes = (Changelist(branchref=b) for b in branches.splitlines()) | 1108 changes = (Changelist(branchref=b) for b in branches.splitlines()) |
| 1093 branches = dict((c.GetBranch(), c.GetIssueURL()) for c in changes) | 1109 branches = dict((c.GetBranch(), c.GetIssueURL()) for c in changes) |
| 1094 alignment = max(5, max(len(b) for b in branches)) | 1110 alignment = max(5, max(len(b) for b in branches)) |
| 1095 print 'Branches associated with reviews:' | 1111 print 'Branches associated with reviews:' |
| 1112 # Adhoc thread pool to request data concurrently. | |
| 1113 output = Queue.Queue() | |
| 1114 | |
| 1115 # Silence upload.py otherwise it becomes unweldly. | |
| 1116 upload.verbosity = 0 | |
| 1117 | |
| 1118 if not options.fast: | |
| 1119 def fetch(b): | |
| 1120 c = Changelist(branchref=b) | |
| 1121 i = c.GetIssueURL() | |
| 1122 try: | |
| 1123 props = c.GetIssueProperties() | |
| 1124 r = c.GetApprovingReviewers() if i else None | |
| 1125 if not props.get('messages'): | |
| 1126 r = None | |
| 1127 except urllib2.HTTPError: | |
| 1128 # The issue probably doesn't exist anymore. | |
| 1129 i += ' (broken)' | |
| 1130 r = None | |
| 1131 output.put((b, i, r)) | |
| 1132 | |
| 1133 threads = [threading.Thread(target=fetch, args=(b,)) for b in branches] | |
| 1134 for t in threads: | |
| 1135 t.daemon = True | |
| 1136 t.start() | |
| 1137 else: | |
| 1138 # Do not use GetApprovingReviewers(), since it requires an HTTP request. | |
| 1139 for b in branches: | |
| 1140 c = Changelist(branchref=b) | |
| 1141 output.put((b, c.GetIssue(), None)) | |
| 1142 | |
| 1143 tmp = {} | |
| 1144 alignment = max(5, max(len(ShortBranchName(b)) for b in branches)) | |
| 1096 for branch in sorted(branches): | 1145 for branch in sorted(branches): |
| 1097 print " %*s: %s" % (alignment, branch, branches[branch]) | 1146 while branch not in tmp: |
| 1147 b, i, r = output.get() | |
| 1148 tmp[b] = (i, r) | |
| 1149 issue, reviewers = tmp.pop(branch) | |
| 1150 if not issue: | |
| 1151 color = Fore.WHITE | |
| 1152 elif reviewers: | |
| 1153 # Was approved. | |
| 1154 color = Fore.GREEN | |
| 1155 elif reviewers is None: | |
| 1156 # No message was sent. | |
| 1157 color = Fore.RED | |
| 1158 else: | |
| 1159 color = Fore.BLUE | |
| 1160 print ' %*s: %s%s%s' % ( | |
| 1161 alignment, ShortBranchName(branch), color, issue, Fore.RESET) | |
| 1162 | |
| 1098 cl = Changelist() | 1163 cl = Changelist() |
| 1099 print | 1164 print |
| 1100 print 'Current branch:', | 1165 print 'Current branch:', |
| 1101 if not cl.GetIssue(): | 1166 if not cl.GetIssue(): |
| 1102 print 'no issue assigned.' | 1167 print 'no issue assigned.' |
| 1103 return 0 | 1168 return 0 |
| 1104 print cl.GetBranch() | 1169 print cl.GetBranch() |
| 1105 print 'Issue number: %s (%s)' % (cl.GetIssue(), cl.GetIssueURL()) | 1170 print 'Issue number: %s (%s)' % (cl.GetIssue(), cl.GetIssueURL()) |
| 1106 print 'Issue description:' | 1171 print 'Issue description:' |
| 1107 print cl.GetDescription(pretty=True) | 1172 print cl.GetDescription(pretty=True) |
| (...skipping 21 matching lines...) Expand all Loading... | |
| 1129 | 1194 |
| 1130 | 1195 |
| 1131 def CMDcomments(parser, args): | 1196 def CMDcomments(parser, args): |
| 1132 """show review comments of the current changelist""" | 1197 """show review comments of the current changelist""" |
| 1133 (_, args) = parser.parse_args(args) | 1198 (_, args) = parser.parse_args(args) |
| 1134 if args: | 1199 if args: |
| 1135 parser.error('Unsupported argument: %s' % args) | 1200 parser.error('Unsupported argument: %s' % args) |
| 1136 | 1201 |
| 1137 cl = Changelist() | 1202 cl = Changelist() |
| 1138 if cl.GetIssue(): | 1203 if cl.GetIssue(): |
| 1139 data = cl.RpcServer().get_issue_properties(cl.GetIssue(), True) | 1204 data = cl.GetIssueProperties() |
| 1140 for message in sorted(data['messages'], key=lambda x: x['date']): | 1205 for message in sorted(data['messages'], key=lambda x: x['date']): |
| 1141 if message['disapproval']: | 1206 if message['disapproval']: |
| 1142 color = Fore.RED | 1207 color = Fore.RED |
| 1143 elif message['approval']: | 1208 elif message['approval']: |
| 1144 color = Fore.GREEN | 1209 color = Fore.GREEN |
| 1145 elif message['sender'] == data['owner_email']: | 1210 elif message['sender'] == data['owner_email']: |
| 1146 color = Fore.MAGENTA | 1211 color = Fore.MAGENTA |
| 1147 else: | 1212 else: |
| 1148 color = Fore.BLUE | 1213 color = Fore.BLUE |
| 1149 print '\n%s%s %s%s' % ( | 1214 print '\n%s%s %s%s' % ( |
| (...skipping 277 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 1427 hook_results = cl.RunHook(committing=False, | 1492 hook_results = cl.RunHook(committing=False, |
| 1428 may_prompt=not options.force, | 1493 may_prompt=not options.force, |
| 1429 verbose=options.verbose, | 1494 verbose=options.verbose, |
| 1430 change=change) | 1495 change=change) |
| 1431 if not hook_results.should_continue(): | 1496 if not hook_results.should_continue(): |
| 1432 return 1 | 1497 return 1 |
| 1433 if not options.reviewers and hook_results.reviewers: | 1498 if not options.reviewers and hook_results.reviewers: |
| 1434 options.reviewers = hook_results.reviewers.split(',') | 1499 options.reviewers = hook_results.reviewers.split(',') |
| 1435 | 1500 |
| 1436 if cl.GetIssue(): | 1501 if cl.GetIssue(): |
| 1437 latest_patchset = cl.GetMostRecentPatchset(cl.GetIssue()) | 1502 latest_patchset = cl.GetMostRecentPatchset() |
| 1438 local_patchset = cl.GetPatchset() | 1503 local_patchset = cl.GetPatchset() |
| 1439 if latest_patchset and local_patchset and local_patchset != latest_patchset: | 1504 if latest_patchset and local_patchset and local_patchset != latest_patchset: |
| 1440 print ('The last upload made from this repository was patchset #%d but ' | 1505 print ('The last upload made from this repository was patchset #%d but ' |
| 1441 'the most recent patchset on the server is #%d.' | 1506 'the most recent patchset on the server is #%d.' |
| 1442 % (local_patchset, latest_patchset)) | 1507 % (local_patchset, latest_patchset)) |
| 1443 print ('Uploading will still work, but if you\'ve uploaded to this issue ' | 1508 print ('Uploading will still work, but if you\'ve uploaded to this issue ' |
| 1444 'from another machine or branch the patch you\'re uploading now ' | 1509 'from another machine or branch the patch you\'re uploading now ' |
| 1445 'might not include those changes.') | 1510 'might not include those changes.') |
| 1446 ask_for_data('About to upload; enter to confirm.') | 1511 ask_for_data('About to upload; enter to confirm.') |
| 1447 | 1512 |
| (...skipping 218 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 1666 return 1 | 1731 return 1 |
| 1667 viewvc_url = settings.GetViewVCUrl() | 1732 viewvc_url = settings.GetViewVCUrl() |
| 1668 if viewvc_url and revision: | 1733 if viewvc_url and revision: |
| 1669 change_desc.append_footer('Committed: ' + viewvc_url + revision) | 1734 change_desc.append_footer('Committed: ' + viewvc_url + revision) |
| 1670 elif revision: | 1735 elif revision: |
| 1671 change_desc.append_footer('Committed: ' + revision) | 1736 change_desc.append_footer('Committed: ' + revision) |
| 1672 print ('Closing issue ' | 1737 print ('Closing issue ' |
| 1673 '(you may be prompted for your codereview password)...') | 1738 '(you may be prompted for your codereview password)...') |
| 1674 cl.UpdateDescription(change_desc.description) | 1739 cl.UpdateDescription(change_desc.description) |
| 1675 cl.CloseIssue() | 1740 cl.CloseIssue() |
| 1676 props = cl.RpcServer().get_issue_properties(cl.GetIssue(), False) | 1741 props = cl.GetIssueProperties() |
| 1677 patch_num = len(props['patchsets']) | 1742 patch_num = len(props['patchsets']) |
| 1678 comment = "Committed patchset #%d manually as r%s" % (patch_num, revision) | 1743 comment = "Committed patchset #%d manually as r%s" % (patch_num, revision) |
| 1679 comment += ' (presubmit successful).' if not options.bypass_hooks else '.' | 1744 comment += ' (presubmit successful).' if not options.bypass_hooks else '.' |
| 1680 cl.RpcServer().add_comment(cl.GetIssue(), comment) | 1745 cl.RpcServer().add_comment(cl.GetIssue(), comment) |
| 1681 cl.SetIssue(0) | 1746 cl.SetIssue(0) |
| 1682 | 1747 |
| 1683 if retcode == 0: | 1748 if retcode == 0: |
| 1684 hook = POSTUPSTREAM_HOOK_PATTERN % cmd | 1749 hook = POSTUPSTREAM_HOOK_PATTERN % cmd |
| 1685 if os.path.isfile(hook): | 1750 if os.path.isfile(hook): |
| 1686 RunCommand([hook, base_branch], error_ok=True) | 1751 RunCommand([hook, base_branch], error_ok=True) |
| (...skipping 252 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 1939 if any('triggered' in b for b in builders_and_tests): | 2004 if any('triggered' in b for b in builders_and_tests): |
| 1940 print >> sys.stderr, ( | 2005 print >> sys.stderr, ( |
| 1941 'ERROR You are trying to send a job to a triggered bot. This type of' | 2006 'ERROR You are trying to send a job to a triggered bot. This type of' |
| 1942 ' bot requires an\ninitial job from a parent (usually a builder). ' | 2007 ' bot requires an\ninitial job from a parent (usually a builder). ' |
| 1943 'Instead send your job to the parent.\n' | 2008 'Instead send your job to the parent.\n' |
| 1944 'Bot list: %s' % builders_and_tests) | 2009 'Bot list: %s' % builders_and_tests) |
| 1945 return 1 | 2010 return 1 |
| 1946 | 2011 |
| 1947 patchset = cl.GetPatchset() | 2012 patchset = cl.GetPatchset() |
| 1948 if not cl.GetPatchset(): | 2013 if not cl.GetPatchset(): |
| 1949 patchset = cl.GetMostRecentPatchset(cl.GetIssue()) | 2014 patchset = cl.GetMostRecentPatchset() |
| 1950 | 2015 |
| 1951 cl.RpcServer().trigger_try_jobs( | 2016 cl.RpcServer().trigger_try_jobs( |
| 1952 cl.GetIssue(), patchset, options.name, options.clobber, options.revision, | 2017 cl.GetIssue(), patchset, options.name, options.clobber, options.revision, |
| 1953 builders_and_tests) | 2018 builders_and_tests) |
| 1954 print('Tried jobs on:') | 2019 print('Tried jobs on:') |
| 1955 length = max(len(builder) for builder in builders_and_tests) | 2020 length = max(len(builder) for builder in builders_and_tests) |
| 1956 for builder in sorted(builders_and_tests): | 2021 for builder in sorted(builders_and_tests): |
| 1957 print ' %*s: %s' % (length, builder, ','.join(builders_and_tests[builder])) | 2022 print ' %*s: %s' % (length, builder, ','.join(builders_and_tests[builder])) |
| 1958 return 0 | 2023 return 0 |
| 1959 | 2024 |
| (...skipping 217 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 2177 GenUsage(parser, 'help') | 2242 GenUsage(parser, 'help') |
| 2178 return CMDhelp(parser, argv) | 2243 return CMDhelp(parser, argv) |
| 2179 | 2244 |
| 2180 | 2245 |
| 2181 if __name__ == '__main__': | 2246 if __name__ == '__main__': |
| 2182 # These affect sys.stdout so do it outside of main() to simplify mocks in | 2247 # These affect sys.stdout so do it outside of main() to simplify mocks in |
| 2183 # unit testing. | 2248 # unit testing. |
| 2184 fix_encoding.fix_encoding() | 2249 fix_encoding.fix_encoding() |
| 2185 colorama.init() | 2250 colorama.init() |
| 2186 sys.exit(main(sys.argv[1:])) | 2251 sys.exit(main(sys.argv[1:])) |
| OLD | NEW |