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