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 and Gerrit.""" | 8 """A git-command for integrating reviews on Rietveld and Gerrit.""" |
9 | 9 |
10 from __future__ import print_function | 10 from __future__ import print_function |
(...skipping 839 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
850 return ShortBranchName(branchref) | 850 return ShortBranchName(branchref) |
851 return None | 851 return None |
852 | 852 |
853 | 853 |
854 class _CQState(object): | 854 class _CQState(object): |
855 """Enum for states of CL with respect to Commit Queue.""" | 855 """Enum for states of CL with respect to Commit Queue.""" |
856 NONE = 'none' | 856 NONE = 'none' |
857 DRY_RUN = 'dry_run' | 857 DRY_RUN = 'dry_run' |
858 COMMIT = 'commit' | 858 COMMIT = 'commit' |
859 | 859 |
860 ALL_STATES = [NONE, DRY_RUN, COMMIT] | 860 ALL_STATES = [NONE, DRY_RUN, COMMIT] |
tandrii(chromium)
2016/08/16 14:52:10
can you make use of these states?
| |
861 | 861 |
862 | 862 |
863 class _ParsedIssueNumberArgument(object): | 863 class _ParsedIssueNumberArgument(object): |
864 def __init__(self, issue=None, patchset=None, hostname=None): | 864 def __init__(self, issue=None, patchset=None, hostname=None): |
865 self.issue = issue | 865 self.issue = issue |
866 self.patchset = patchset | 866 self.patchset = patchset |
867 self.hostname = hostname | 867 self.hostname = hostname |
868 | 868 |
869 @property | 869 @property |
870 def valid(self): | 870 def valid(self): |
(...skipping 570 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
1441 return self._codereview_impl.SetCQState(new_state) | 1441 return self._codereview_impl.SetCQState(new_state) |
1442 | 1442 |
1443 # Forward methods to codereview specific implementation. | 1443 # Forward methods to codereview specific implementation. |
1444 | 1444 |
1445 def CloseIssue(self): | 1445 def CloseIssue(self): |
1446 return self._codereview_impl.CloseIssue() | 1446 return self._codereview_impl.CloseIssue() |
1447 | 1447 |
1448 def GetStatus(self): | 1448 def GetStatus(self): |
1449 return self._codereview_impl.GetStatus() | 1449 return self._codereview_impl.GetStatus() |
1450 | 1450 |
1451 def IsOpen(self): | |
1452 return self._codereview_impl.IsOpen() | |
1453 | |
1454 def IsInCQ(self): | |
tandrii(chromium)
2016/08/16 14:52:10
s/IsInCQ/GetCQState
and return CQ status from abo
| |
1455 return self._codereview_impl.IsInCQ() | |
1456 | |
1451 def GetCodereviewServer(self): | 1457 def GetCodereviewServer(self): |
1452 return self._codereview_impl.GetCodereviewServer() | 1458 return self._codereview_impl.GetCodereviewServer() |
1453 | 1459 |
1454 def GetApprovingReviewers(self): | 1460 def GetApprovingReviewers(self): |
1455 return self._codereview_impl.GetApprovingReviewers() | 1461 return self._codereview_impl.GetApprovingReviewers() |
1456 | 1462 |
1457 def GetMostRecentPatchset(self): | 1463 def GetMostRecentPatchset(self): |
1458 return self._codereview_impl.GetMostRecentPatchset() | 1464 return self._codereview_impl.GetMostRecentPatchset() |
1459 | 1465 |
1460 def __getattr__(self, attr): | 1466 def __getattr__(self, attr): |
(...skipping 15 matching lines...) Expand all Loading... | |
1476 return getattr(self._changelist, attr) | 1482 return getattr(self._changelist, attr) |
1477 | 1483 |
1478 def GetStatus(self): | 1484 def GetStatus(self): |
1479 """Apply a rough heuristic to give a simple summary of an issue's review | 1485 """Apply a rough heuristic to give a simple summary of an issue's review |
1480 or CQ status, assuming adherence to a common workflow. | 1486 or CQ status, assuming adherence to a common workflow. |
1481 | 1487 |
1482 Returns None if no issue for this branch, or specific string keywords. | 1488 Returns None if no issue for this branch, or specific string keywords. |
1483 """ | 1489 """ |
1484 raise NotImplementedError() | 1490 raise NotImplementedError() |
1485 | 1491 |
1492 def IsOpen(self): | |
1493 """Returns True iff the issue is open.""" | |
1494 raise NotImplementedError() | |
1495 | |
1496 def IsInCQ(self): | |
1497 """Returns True iff the issue is in CQ (does not include dry run).""" | |
1498 raise NotImplementedError() | |
1499 | |
1486 def GetCodereviewServer(self): | 1500 def GetCodereviewServer(self): |
1487 """Returns server URL without end slash, like "https://codereview.com".""" | 1501 """Returns server URL without end slash, like "https://codereview.com".""" |
1488 raise NotImplementedError() | 1502 raise NotImplementedError() |
1489 | 1503 |
1490 def FetchDescription(self): | 1504 def FetchDescription(self): |
1491 """Fetches and returns description from the codereview server.""" | 1505 """Fetches and returns description from the codereview server.""" |
1492 raise NotImplementedError() | 1506 raise NotImplementedError() |
1493 | 1507 |
1494 def GetCodereviewServerSetting(self): | 1508 def GetCodereviewServerSetting(self): |
1495 """Returns git config setting for the codereview server.""" | 1509 """Returns git config setting for the codereview server.""" |
(...skipping 178 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
1674 * 'closed' - closed | 1688 * 'closed' - closed |
1675 """ | 1689 """ |
1676 if not self.GetIssue(): | 1690 if not self.GetIssue(): |
1677 return None | 1691 return None |
1678 | 1692 |
1679 try: | 1693 try: |
1680 props = self.GetIssueProperties() | 1694 props = self.GetIssueProperties() |
1681 except urllib2.HTTPError: | 1695 except urllib2.HTTPError: |
1682 return 'error' | 1696 return 'error' |
1683 | 1697 |
1684 if props.get('closed'): | 1698 if not self.IsOpen(): |
1685 # Issue is closed. | |
1686 return 'closed' | 1699 return 'closed' |
1687 if props.get('commit') and not props.get('cq_dry_run', False): | 1700 if self.IsInCQ(): |
tandrii(chromium)
2016/08/16 14:52:10
and this is great fix.
| |
1688 # Issue is in the commit queue. | |
1689 return 'commit' | 1701 return 'commit' |
1690 | 1702 |
1691 try: | 1703 try: |
1692 reviewers = self.GetApprovingReviewers() | 1704 reviewers = self.GetApprovingReviewers() |
1693 except urllib2.HTTPError: | 1705 except urllib2.HTTPError: |
1694 return 'error' | 1706 return 'error' |
1695 | 1707 |
1696 if reviewers: | 1708 if reviewers: |
1697 # Was LGTM'ed. | 1709 # Was LGTM'ed. |
1698 return 'lgtm' | 1710 return 'lgtm' |
(...skipping 13 matching lines...) Expand all Loading... | |
1712 break | 1724 break |
1713 | 1725 |
1714 if not messages: | 1726 if not messages: |
1715 # No message was sent. | 1727 # No message was sent. |
1716 return 'unsent' | 1728 return 'unsent' |
1717 if messages[-1]['sender'] != props.get('owner_email'): | 1729 if messages[-1]['sender'] != props.get('owner_email'): |
1718 # Non-LGTM reply from non-owner and not CQ bot. | 1730 # Non-LGTM reply from non-owner and not CQ bot. |
1719 return 'reply' | 1731 return 'reply' |
1720 return 'waiting' | 1732 return 'waiting' |
1721 | 1733 |
1734 def IsOpen(self, props=None): | |
1735 if not props: | |
1736 props = self.GetIssueProperties() | |
1737 return not props.get('closed') | |
1738 | |
1739 def IsInCQ(self, props=None): | |
1740 if not props: | |
1741 props = self.GetIssueProperties() | |
1742 return props.get('commit') and not props.get('cq_dry_run') | |
1743 | |
1722 def UpdateDescriptionRemote(self, description): | 1744 def UpdateDescriptionRemote(self, description): |
1723 return self.RpcServer().update_description( | 1745 return self.RpcServer().update_description( |
1724 self.GetIssue(), self.description) | 1746 self.GetIssue(), self.description) |
1725 | 1747 |
1726 def CloseIssue(self): | 1748 def CloseIssue(self): |
1727 return self.RpcServer().close_issue(self.GetIssue()) | 1749 return self.RpcServer().close_issue(self.GetIssue()) |
1728 | 1750 |
1729 def SetFlag(self, flag, value): | 1751 def SetFlag(self, flag, value): |
1730 return self.SetFlags({flag: value}) | 1752 return self.SetFlags({flag: value}) |
1731 | 1753 |
(...skipping 452 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
2184 * 'closed' - abandoned | 2206 * 'closed' - abandoned |
2185 """ | 2207 """ |
2186 if not self.GetIssue(): | 2208 if not self.GetIssue(): |
2187 return None | 2209 return None |
2188 | 2210 |
2189 try: | 2211 try: |
2190 data = self._GetChangeDetail(['DETAILED_LABELS', 'CURRENT_REVISION']) | 2212 data = self._GetChangeDetail(['DETAILED_LABELS', 'CURRENT_REVISION']) |
2191 except httplib.HTTPException: | 2213 except httplib.HTTPException: |
2192 return 'error' | 2214 return 'error' |
2193 | 2215 |
2194 if data['status'] in ('ABANDONED', 'MERGED'): | 2216 if not self.IsOpen(data=data): |
2195 return 'closed' | 2217 return 'closed' |
2196 | 2218 |
2197 cq_label = data['labels'].get('Commit-Queue', {}) | 2219 if self.IsInCQ(data=data): |
2198 if cq_label: | 2220 return 'commit' |
2199 # Vote value is a stringified integer, which we expect from 0 to 2. | |
2200 vote_value = cq_label.get('value', '0') | |
2201 vote_text = cq_label.get('values', {}).get(vote_value, '') | |
2202 if vote_text.lower() == 'commit': | |
2203 return 'commit' | |
2204 | 2221 |
2205 lgtm_label = data['labels'].get('Code-Review', {}) | 2222 lgtm_label = data['labels'].get('Code-Review', {}) |
2206 if lgtm_label: | 2223 if lgtm_label: |
2207 if 'rejected' in lgtm_label: | 2224 if 'rejected' in lgtm_label: |
2208 return 'not lgtm' | 2225 return 'not lgtm' |
2209 if 'approved' in lgtm_label: | 2226 if 'approved' in lgtm_label: |
2210 return 'lgtm' | 2227 return 'lgtm' |
2211 | 2228 |
2212 if not data.get('reviewers', {}).get('REVIEWER', []): | 2229 if not data.get('reviewers', {}).get('REVIEWER', []): |
2213 return 'unsent' | 2230 return 'unsent' |
2214 | 2231 |
2215 messages = data.get('messages', []) | 2232 messages = data.get('messages', []) |
2216 if messages: | 2233 if messages: |
2217 owner = data['owner'].get('_account_id') | 2234 owner = data['owner'].get('_account_id') |
2218 last_message_author = messages[-1].get('author', {}).get('_account_id') | 2235 last_message_author = messages[-1].get('author', {}).get('_account_id') |
2219 if owner != last_message_author: | 2236 if owner != last_message_author: |
2220 # Some reply from non-owner. | 2237 # Some reply from non-owner. |
2221 return 'reply' | 2238 return 'reply' |
2222 | 2239 |
2223 return 'waiting' | 2240 return 'waiting' |
2224 | 2241 |
2242 def IsOpen(self, data=None): | |
2243 if not data: | |
2244 data = self._GetChangeDetail(['DETAILED_LABELS', 'CURRENT_REVISION']) | |
2245 return data['status'] not in ('ABANDONED', 'MERGED') | |
2246 | |
2247 def IsInCQ(self, data=None): | |
2248 if not data: | |
2249 data = self._GetChangeDetail(['DETAILED_LABELS', 'CURRENT_REVISION']) | |
2250 cq_label = data['labels'].get('Commit-Queue', {}) | |
2251 if cq_label: | |
2252 # Vote value is a stringified integer, which we expect from 0 to 2. | |
2253 vote_value = cq_label.get('value', '0') | |
2254 vote_text = cq_label.get('values', {}).get(vote_value, '') | |
2255 if vote_text.lower() == 'commit': | |
2256 return True | |
2257 return False | |
2258 | |
2225 def GetMostRecentPatchset(self): | 2259 def GetMostRecentPatchset(self): |
2226 data = self._GetChangeDetail(['CURRENT_REVISION']) | 2260 data = self._GetChangeDetail(['CURRENT_REVISION']) |
2227 return data['revisions'][data['current_revision']]['_number'] | 2261 return data['revisions'][data['current_revision']]['_number'] |
2228 | 2262 |
2229 def FetchDescription(self): | 2263 def FetchDescription(self): |
2230 data = self._GetChangeDetail(['CURRENT_REVISION']) | 2264 data = self._GetChangeDetail(['CURRENT_REVISION']) |
2231 current_rev = data['current_revision'] | 2265 current_rev = data['current_revision'] |
2232 url = data['revisions'][current_rev]['fetch']['http']['url'] | 2266 url = data['revisions'][current_rev]['fetch']['http']['url'] |
2233 return gerrit_util.GetChangeDescriptionFromGitiles(url, current_rev) | 2267 return gerrit_util.GetChangeDescriptionFromGitiles(url, current_rev) |
2234 | 2268 |
(...skipping 2817 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
5052 def CMDlol(parser, args): | 5086 def CMDlol(parser, args): |
5053 # This command is intentionally undocumented. | 5087 # This command is intentionally undocumented. |
5054 print(zlib.decompress(base64.b64decode( | 5088 print(zlib.decompress(base64.b64decode( |
5055 'eNptkLEOwyAMRHe+wupCIqW57v0Vq84WqWtXyrcXnCBsmgMJ+/SSAxMZgRB6NzE' | 5089 'eNptkLEOwyAMRHe+wupCIqW57v0Vq84WqWtXyrcXnCBsmgMJ+/SSAxMZgRB6NzE' |
5056 'E2ObgCKJooYdu4uAQVffUEoE1sRQLxAcqzd7uK2gmStrll1ucV3uZyaY5sXyDd9' | 5090 'E2ObgCKJooYdu4uAQVffUEoE1sRQLxAcqzd7uK2gmStrll1ucV3uZyaY5sXyDd9' |
5057 'JAnN+lAXsOMJ90GANAi43mq5/VeeacylKVgi8o6F1SC63FxnagHfJUTfUYdCR/W' | 5091 'JAnN+lAXsOMJ90GANAi43mq5/VeeacylKVgi8o6F1SC63FxnagHfJUTfUYdCR/W' |
5058 'Ofe+0dHL7PicpytKP750Fh1q2qnLVof4w8OZWNY'))) | 5092 'Ofe+0dHL7PicpytKP750Fh1q2qnLVof4w8OZWNY'))) |
5059 return 0 | 5093 return 0 |
5060 | 5094 |
5061 | 5095 |
5096 def CMDplumbing(parser, args): | |
5097 """Returns info about the CL in machine-readable form.""" | |
5098 parser.add_option('--json', help='Path to output JSON file') | |
5099 | |
5100 auth.add_auth_options(parser) | |
5101 | |
5102 _add_codereview_select_options(parser) | |
5103 options, args = parser.parse_args(args) | |
5104 _process_codereview_select_options(parser, options) | |
5105 | |
5106 auth_config = auth.extract_auth_config_from_options(options) | |
5107 | |
5108 cl = Changelist(auth_config=auth_config, codereview=options.forced_codereview) | |
5109 | |
5110 result = { | |
5111 'open': cl.IsOpen(), | |
5112 'commit_queue': cl.IsInCQ(), | |
5113 } | |
5114 | |
5115 if options.json: | |
5116 with open(options.json, 'w') as f: | |
5117 json.dump(result, f) | |
5118 else: | |
5119 print(json.dumps(result, sort_keys=True, indent=4)) | |
5120 | |
5121 return 0 | |
5122 | |
5123 | |
5062 class OptionParser(optparse.OptionParser): | 5124 class OptionParser(optparse.OptionParser): |
5063 """Creates the option parse and add --verbose support.""" | 5125 """Creates the option parse and add --verbose support.""" |
5064 def __init__(self, *args, **kwargs): | 5126 def __init__(self, *args, **kwargs): |
5065 optparse.OptionParser.__init__( | 5127 optparse.OptionParser.__init__( |
5066 self, *args, prog='git cl', version=__version__, **kwargs) | 5128 self, *args, prog='git cl', version=__version__, **kwargs) |
5067 self.add_option( | 5129 self.add_option( |
5068 '-v', '--verbose', action='count', default=0, | 5130 '-v', '--verbose', action='count', default=0, |
5069 help='Use 2 times for more debugging info') | 5131 help='Use 2 times for more debugging info') |
5070 | 5132 |
5071 def parse_args(self, args=None, values=None): | 5133 def parse_args(self, args=None, values=None): |
(...skipping 31 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
5103 if __name__ == '__main__': | 5165 if __name__ == '__main__': |
5104 # These affect sys.stdout so do it outside of main() to simplify mocks in | 5166 # These affect sys.stdout so do it outside of main() to simplify mocks in |
5105 # unit testing. | 5167 # unit testing. |
5106 fix_encoding.fix_encoding() | 5168 fix_encoding.fix_encoding() |
5107 setup_color.init() | 5169 setup_color.init() |
5108 try: | 5170 try: |
5109 sys.exit(main(sys.argv[1:])) | 5171 sys.exit(main(sys.argv[1:])) |
5110 except KeyboardInterrupt: | 5172 except KeyboardInterrupt: |
5111 sys.stderr.write('interrupted\n') | 5173 sys.stderr.write('interrupted\n') |
5112 sys.exit(1) | 5174 sys.exit(1) |
OLD | NEW |