 Chromium Code Reviews
 Chromium Code Reviews Issue 2250093002:
  git cl: add plumbing command to get metadata about CLs in machine-readable form  (Closed) 
  Base URL: svn://svn.chromium.org/chrome/trunk/tools/depot_tools
    
  
    Issue 2250093002:
  git cl: add plumbing command to get metadata about CLs in machine-readable form  (Closed) 
  Base URL: svn://svn.chromium.org/chrome/trunk/tools/depot_tools| 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 |