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): |
|
iannucci
2013/07/23 02:07:28
issue is unused?
M-A Ruel
2013/07/23 14:32:42
Oops. Did a bit of surgery to fix the thing.
| |
| 679 return self.RpcServer().get_issue_properties( | 682 return self.GetIssueProperties()['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): | |
| 689 if self._props is None: | |
| 690 if not self.GetIssue(): | |
| 691 self._props = {} | |
| 692 else: | |
| 693 self._props = self.RpcServer().get_issue_properties( | |
| 694 self.GetIssue(), True) | |
| 695 return self._props | |
| 696 | |
| 686 def GetApprovingReviewers(self): | 697 def GetApprovingReviewers(self): |
| 687 return get_approving_reviewers( | 698 return get_approving_reviewers(self.GetIssueProperties()) |
| 688 self.RpcServer().get_issue_properties(self.GetIssue(), True)) | |
| 689 | 699 |
| 690 def SetIssue(self, issue): | 700 def SetIssue(self, issue): |
| 691 """Set this branch's issue. If issue=0, clears the issue.""" | 701 """Set this branch's issue. If issue=0, clears the issue.""" |
| 692 if issue: | 702 if issue: |
| 693 RunGit(['config', self._IssueSetting(), str(issue)]) | 703 RunGit(['config', self._IssueSetting(), str(issue)]) |
| 694 if self.rietveld_server: | 704 if self.rietveld_server: |
| 695 RunGit(['config', self._RietveldServer(), self.rietveld_server]) | 705 RunGit(['config', self._RietveldServer(), self.rietveld_server]) |
| 696 else: | 706 else: |
| 697 RunGit(['config', '--unset', self._IssueSetting()]) | 707 RunGit(['config', '--unset', self._IssueSetting()]) |
| 698 self.SetPatchset(0) | 708 self.SetPatchset(0) |
| (...skipping 358 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 1057 else: | 1067 else: |
| 1058 print("Setting base-url to %s" % args[0]) | 1068 print("Setting base-url to %s" % args[0]) |
| 1059 return RunGit(['config', 'branch.%s.base-url' % branch, args[0]], | 1069 return RunGit(['config', 'branch.%s.base-url' % branch, args[0]], |
| 1060 error_ok=False).strip() | 1070 error_ok=False).strip() |
| 1061 | 1071 |
| 1062 | 1072 |
| 1063 def CMDstatus(parser, args): | 1073 def CMDstatus(parser, args): |
| 1064 """show status of changelists""" | 1074 """show status of changelists""" |
| 1065 parser.add_option('--field', | 1075 parser.add_option('--field', |
| 1066 help='print only specific field (desc|id|patch|url)') | 1076 help='print only specific field (desc|id|patch|url)') |
| 1077 parser.add_option('-f', '--fast', action='store_true', | |
| 1078 help='Do not retrieve review status') | |
| 1067 (options, args) = parser.parse_args(args) | 1079 (options, args) = parser.parse_args(args) |
| 1068 | 1080 |
| 1069 if options.field: | 1081 if options.field: |
| 1070 cl = Changelist() | 1082 cl = Changelist() |
| 1071 if options.field.startswith('desc'): | 1083 if options.field.startswith('desc'): |
| 1072 print cl.GetDescription() | 1084 print cl.GetDescription() |
| 1073 elif options.field == 'id': | 1085 elif options.field == 'id': |
| 1074 issueid = cl.GetIssue() | 1086 issueid = cl.GetIssue() |
| 1075 if issueid: | 1087 if issueid: |
| 1076 print issueid | 1088 print issueid |
| 1077 elif options.field == 'patch': | 1089 elif options.field == 'patch': |
| 1078 patchset = cl.GetPatchset() | 1090 patchset = cl.GetPatchset() |
| 1079 if patchset: | 1091 if patchset: |
| 1080 print patchset | 1092 print patchset |
| 1081 elif options.field == 'url': | 1093 elif options.field == 'url': |
| 1082 url = cl.GetIssueURL() | 1094 url = cl.GetIssueURL() |
| 1083 if url: | 1095 if url: |
| 1084 print url | 1096 print url |
| 1085 return 0 | 1097 return 0 |
| 1086 | 1098 |
| 1087 branches = RunGit(['for-each-ref', '--format=%(refname)', 'refs/heads']) | 1099 branches = RunGit(['for-each-ref', '--format=%(refname)', 'refs/heads']) |
| 1088 if not branches: | 1100 if not branches: |
| 1089 print('No local branch found.') | 1101 print('No local branch found.') |
| 1090 return 0 | 1102 return 0 |
| 1091 | 1103 |
| 1092 changes = (Changelist(branchref=b) for b in branches.splitlines()) | 1104 changes = (Changelist(branchref=b) for b in branches.splitlines()) |
| 1093 branches = dict((c.GetBranch(), c.GetIssueURL()) for c in changes) | 1105 branches = dict((c.GetBranch(), c.GetIssueURL()) for c in changes) |
| 1094 alignment = max(5, max(len(b) for b in branches)) | 1106 alignment = max(5, max(len(b) for b in branches)) |
| 1095 print 'Branches associated with reviews:' | 1107 print 'Branches associated with reviews:' |
| 1108 # Adhoc thread pool to request data concurrently. | |
| 1109 output = Queue.Queue() | |
| 1110 | |
| 1111 # Silence upload.py otherwise it becomes unweldly. | |
| 1112 upload.verbosity = 0 | |
| 1113 | |
| 1114 if not options.fast: | |
| 1115 def fetch(b): | |
| 1116 c = Changelist(branchref=b) | |
| 1117 i = c.GetIssueURL() | |
| 1118 try: | |
| 1119 props = c.GetIssueProperties() | |
| 1120 r = c.GetApprovingReviewers() if i else None | |
| 1121 if not props.get('messages'): | |
| 1122 r = None | |
| 1123 except urllib2.HTTPError: | |
| 1124 # The issue probably doesn't exist anymore. | |
| 1125 i += ' (broken)' | |
| 1126 r = None | |
| 1127 output.put((b, i, r)) | |
| 1128 | |
| 1129 threads = [threading.Thread(target=fetch, args=(b,)) for b in branches] | |
| 1130 for t in threads: | |
| 1131 t.daemon = True | |
| 1132 t.start() | |
| 1133 else: | |
| 1134 # Do not use GetApprovingReviewers(), since it requires an HTTP request. | |
| 1135 for b in branches: | |
| 1136 c = Changelist(branchref=b) | |
| 1137 output.put((b, c.GetIssue(), None)) | |
| 1138 | |
| 1139 tmp = {} | |
| 1140 alignment = max(5, max(len(ShortBranchName(b)) for b in branches)) | |
| 1096 for branch in sorted(branches): | 1141 for branch in sorted(branches): |
| 1097 print " %*s: %s" % (alignment, branch, branches[branch]) | 1142 while branch not in tmp: |
| 1143 b, i, r = output.get() | |
| 1144 tmp[b] = (i, r) | |
| 1145 issue, reviewers = tmp.pop(branch) | |
| 1146 if not issue: | |
| 1147 color = Fore.WHITE | |
| 1148 elif reviewers: | |
| 1149 # Was approved. | |
| 1150 color = Fore.GREEN | |
| 1151 elif reviewers is None: | |
| 1152 # No message was sent. | |
| 1153 color = Fore.RED | |
| 1154 else: | |
| 1155 color = Fore.BLUE | |
| 1156 print ' %*s: %s%s%s' % ( | |
| 1157 alignment, ShortBranchName(branch), color, issue, Fore.RESET) | |
| 1158 | |
| 1098 cl = Changelist() | 1159 cl = Changelist() |
| 1099 print | 1160 print |
| 1100 print 'Current branch:', | 1161 print 'Current branch:', |
| 1101 if not cl.GetIssue(): | 1162 if not cl.GetIssue(): |
| 1102 print 'no issue assigned.' | 1163 print 'no issue assigned.' |
| 1103 return 0 | 1164 return 0 |
| 1104 print cl.GetBranch() | 1165 print cl.GetBranch() |
| 1105 print 'Issue number: %s (%s)' % (cl.GetIssue(), cl.GetIssueURL()) | 1166 print 'Issue number: %s (%s)' % (cl.GetIssue(), cl.GetIssueURL()) |
| 1106 print 'Issue description:' | 1167 print 'Issue description:' |
| 1107 print cl.GetDescription(pretty=True) | 1168 print cl.GetDescription(pretty=True) |
| (...skipping 21 matching lines...) Expand all Loading... | |
| 1129 | 1190 |
| 1130 | 1191 |
| 1131 def CMDcomments(parser, args): | 1192 def CMDcomments(parser, args): |
| 1132 """show review comments of the current changelist""" | 1193 """show review comments of the current changelist""" |
| 1133 (_, args) = parser.parse_args(args) | 1194 (_, args) = parser.parse_args(args) |
| 1134 if args: | 1195 if args: |
| 1135 parser.error('Unsupported argument: %s' % args) | 1196 parser.error('Unsupported argument: %s' % args) |
| 1136 | 1197 |
| 1137 cl = Changelist() | 1198 cl = Changelist() |
| 1138 if cl.GetIssue(): | 1199 if cl.GetIssue(): |
| 1139 data = cl.RpcServer().get_issue_properties(cl.GetIssue(), True) | 1200 data = cl.GetIssueProperties() |
| 1140 for message in sorted(data['messages'], key=lambda x: x['date']): | 1201 for message in sorted(data['messages'], key=lambda x: x['date']): |
| 1141 if message['disapproval']: | 1202 if message['disapproval']: |
| 1142 color = Fore.RED | 1203 color = Fore.RED |
| 1143 elif message['approval']: | 1204 elif message['approval']: |
| 1144 color = Fore.GREEN | 1205 color = Fore.GREEN |
| 1145 elif message['sender'] == data['owner_email']: | 1206 elif message['sender'] == data['owner_email']: |
| 1146 color = Fore.MAGENTA | 1207 color = Fore.MAGENTA |
| 1147 else: | 1208 else: |
| 1148 color = Fore.BLUE | 1209 color = Fore.BLUE |
| 1149 print '\n%s%s %s%s' % ( | 1210 print '\n%s%s %s%s' % ( |
| (...skipping 516 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 1666 return 1 | 1727 return 1 |
| 1667 viewvc_url = settings.GetViewVCUrl() | 1728 viewvc_url = settings.GetViewVCUrl() |
| 1668 if viewvc_url and revision: | 1729 if viewvc_url and revision: |
| 1669 change_desc.append_footer('Committed: ' + viewvc_url + revision) | 1730 change_desc.append_footer('Committed: ' + viewvc_url + revision) |
| 1670 elif revision: | 1731 elif revision: |
| 1671 change_desc.append_footer('Committed: ' + revision) | 1732 change_desc.append_footer('Committed: ' + revision) |
| 1672 print ('Closing issue ' | 1733 print ('Closing issue ' |
| 1673 '(you may be prompted for your codereview password)...') | 1734 '(you may be prompted for your codereview password)...') |
| 1674 cl.UpdateDescription(change_desc.description) | 1735 cl.UpdateDescription(change_desc.description) |
| 1675 cl.CloseIssue() | 1736 cl.CloseIssue() |
| 1676 props = cl.RpcServer().get_issue_properties(cl.GetIssue(), False) | 1737 props = cl.GetIssueProperties() |
| 1677 patch_num = len(props['patchsets']) | 1738 patch_num = len(props['patchsets']) |
| 1678 comment = "Committed patchset #%d manually as r%s" % (patch_num, revision) | 1739 comment = "Committed patchset #%d manually as r%s" % (patch_num, revision) |
| 1679 comment += ' (presubmit successful).' if not options.bypass_hooks else '.' | 1740 comment += ' (presubmit successful).' if not options.bypass_hooks else '.' |
| 1680 cl.RpcServer().add_comment(cl.GetIssue(), comment) | 1741 cl.RpcServer().add_comment(cl.GetIssue(), comment) |
| 1681 cl.SetIssue(0) | 1742 cl.SetIssue(0) |
| 1682 | 1743 |
| 1683 if retcode == 0: | 1744 if retcode == 0: |
| 1684 hook = POSTUPSTREAM_HOOK_PATTERN % cmd | 1745 hook = POSTUPSTREAM_HOOK_PATTERN % cmd |
| 1685 if os.path.isfile(hook): | 1746 if os.path.isfile(hook): |
| 1686 RunCommand([hook, base_branch], error_ok=True) | 1747 RunCommand([hook, base_branch], error_ok=True) |
| (...skipping 490 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 2177 GenUsage(parser, 'help') | 2238 GenUsage(parser, 'help') |
| 2178 return CMDhelp(parser, argv) | 2239 return CMDhelp(parser, argv) |
| 2179 | 2240 |
| 2180 | 2241 |
| 2181 if __name__ == '__main__': | 2242 if __name__ == '__main__': |
| 2182 # These affect sys.stdout so do it outside of main() to simplify mocks in | 2243 # These affect sys.stdout so do it outside of main() to simplify mocks in |
| 2183 # unit testing. | 2244 # unit testing. |
| 2184 fix_encoding.fix_encoding() | 2245 fix_encoding.fix_encoding() |
| 2185 colorama.init() | 2246 colorama.init() |
| 2186 sys.exit(main(sys.argv[1:])) | 2247 sys.exit(main(sys.argv[1:])) |
| OLD | NEW |