| 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 |