mirror of
https://chromium.googlesource.com/chromium/tools/depot_tools.git
synced 2026-01-11 18:51:29 +00:00
Add commit_queue.py tool to toggle the bit of the commit queue from command line
Add "git cl set_commit" command to set the flag more easily. Add --commit to "git cl upload" to streamline workflow even more. Continue conversion to Rietveld object. R=dpranke@chromium.org BUG= TEST= Review URL: http://codereview.chromium.org/7084037 git-svn-id: svn://svn.chromium.org/chrome/trunk/tools/depot_tools@87253 0039d316-1c4b-4281-b951-d872f2087c98
This commit is contained in:
187
commit_queue.py
Executable file
187
commit_queue.py
Executable file
@@ -0,0 +1,187 @@
|
||||
#!/usr/bin/env python
|
||||
# Copyright (c) 2011 The Chromium Authors. All rights reserved.
|
||||
# Use of this source code is governed by a BSD-style license that can be
|
||||
# found in the LICENSE file.
|
||||
|
||||
"""Access the commit queue from the command line.
|
||||
"""
|
||||
|
||||
__version__ = '0.1'
|
||||
|
||||
import functools
|
||||
import logging
|
||||
import optparse
|
||||
import os
|
||||
import sys
|
||||
import urllib2
|
||||
|
||||
import breakpad # pylint: disable=W0611
|
||||
|
||||
import fix_encoding
|
||||
import rietveld
|
||||
|
||||
|
||||
def usage(more):
|
||||
def hook(fn):
|
||||
fn.func_usage_more = more
|
||||
return fn
|
||||
return hook
|
||||
|
||||
|
||||
def need_issue(fn):
|
||||
"""Post-parse args to create a Rietveld object."""
|
||||
@functools.wraps(fn)
|
||||
def hook(parser, args, *extra_args, **kwargs):
|
||||
old_parse_args = parser.parse_args
|
||||
|
||||
def new_parse_args(args):
|
||||
options, args = old_parse_args(args)
|
||||
if not options.issue:
|
||||
parser.error('Require --issue')
|
||||
obj = rietveld.Rietveld(options.server, options.user, None)
|
||||
return options, args, obj
|
||||
|
||||
parser.parse_args = new_parse_args
|
||||
|
||||
parser.add_option(
|
||||
'-u', '--user',
|
||||
metavar='U',
|
||||
default=os.environ.get('EMAIL_ADDRESS', None),
|
||||
help='Email address, default: %default')
|
||||
parser.add_option(
|
||||
'-i', '--issue',
|
||||
metavar='I',
|
||||
type='int',
|
||||
help='Rietveld issue number')
|
||||
parser.add_option(
|
||||
'-s',
|
||||
'--server',
|
||||
metavar='S',
|
||||
default='http://codereview.chromium.org',
|
||||
help='Rietveld server, default: %default')
|
||||
|
||||
# Call the original function with the modified parser.
|
||||
return fn(parser, args, *extra_args, **kwargs)
|
||||
|
||||
hook.func_usage_more = '[options]'
|
||||
return hook
|
||||
|
||||
|
||||
def set_commit(obj, issue, flag):
|
||||
"""Sets the commit bit flag on an issue."""
|
||||
try:
|
||||
patchset = obj.get_issue_properties(issue, False)['patchsets'][-1]
|
||||
print obj.set_flag(issue, patchset, 'commit', flag)
|
||||
except urllib2.HTTPError, e:
|
||||
if e.code == 404:
|
||||
print >> sys.stderr, 'Issue %d doesn\'t exist.' % issue
|
||||
elif e.code == 403:
|
||||
print >> sys.stderr, 'Access denied to issue %d.' % issue
|
||||
else:
|
||||
raise
|
||||
return 1
|
||||
|
||||
@need_issue
|
||||
def CMDset(parser, args):
|
||||
"""Sets the commit bit."""
|
||||
options, args, obj = parser.parse_args(args)
|
||||
if args:
|
||||
parser.error('Unrecognized args: %s' % ' '.join(args))
|
||||
return set_commit(obj, options.issue, '1')
|
||||
|
||||
|
||||
@need_issue
|
||||
def CMDclear(parser, args):
|
||||
"""Clears the commit bit."""
|
||||
options, args, obj = parser.parse_args(args)
|
||||
if args:
|
||||
parser.error('Unrecognized args: %s' % ' '.join(args))
|
||||
return set_commit(obj, options.issue, '0')
|
||||
|
||||
|
||||
###############################################################################
|
||||
## Boilerplate code
|
||||
|
||||
|
||||
def gen_parser():
|
||||
"""Returns an OptionParser instance with default options.
|
||||
|
||||
It should be then processed with gen_usage() before being used.
|
||||
"""
|
||||
parser = optparse.OptionParser(version=__version__)
|
||||
# Remove description formatting
|
||||
parser.format_description = lambda x: parser.description
|
||||
# Add common parsing.
|
||||
old_parser_args = parser.parse_args
|
||||
|
||||
def Parse(*args, **kwargs):
|
||||
options, args = old_parser_args(*args, **kwargs)
|
||||
logging.basicConfig(
|
||||
level=[logging.WARNING, logging.INFO, logging.DEBUG][
|
||||
min(2, options.verbose)],
|
||||
format='%(levelname)s %(filename)s(%(lineno)d): %(message)s')
|
||||
return options, args
|
||||
|
||||
parser.parse_args = Parse
|
||||
|
||||
parser.add_option(
|
||||
'-v', '--verbose', action='count', default=0,
|
||||
help='Use multiple times to increase logging level')
|
||||
return parser
|
||||
|
||||
|
||||
def Command(name):
|
||||
return getattr(sys.modules[__name__], 'CMD' + name, None)
|
||||
|
||||
|
||||
@usage('<command>')
|
||||
def CMDhelp(parser, args):
|
||||
"""Print list of commands or use 'help <command>'."""
|
||||
# Strip out the help command description and replace it with the module
|
||||
# docstring.
|
||||
parser.description = sys.modules[__name__].__doc__
|
||||
parser.description += '\nCommands are:\n' + '\n'.join(
|
||||
' %-12s %s' % (
|
||||
fn[3:], Command(fn[3:]).__doc__.split('\n', 1)[0].rstrip('.'))
|
||||
for fn in dir(sys.modules[__name__]) if fn.startswith('CMD'))
|
||||
|
||||
_, args = parser.parse_args(args)
|
||||
if len(args) == 1 and args[0] != 'help':
|
||||
return main(args + ['--help'])
|
||||
parser.print_help()
|
||||
return 0
|
||||
|
||||
|
||||
def gen_usage(parser, command):
|
||||
"""Modifies an OptionParser object with the command's documentation.
|
||||
|
||||
The documentation is taken from the function's docstring.
|
||||
"""
|
||||
obj = Command(command)
|
||||
more = getattr(obj, 'func_usage_more')
|
||||
# OptParser.description prefer nicely non-formatted strings.
|
||||
parser.description = obj.__doc__ + '\n'
|
||||
parser.set_usage('usage: %%prog %s %s' % (command, more))
|
||||
|
||||
|
||||
def main(args=None):
|
||||
# Do it late so all commands are listed.
|
||||
# pylint: disable=E1101
|
||||
parser = gen_parser()
|
||||
if args is None:
|
||||
args = sys.argv[1:]
|
||||
if args:
|
||||
command = Command(args[0])
|
||||
if command:
|
||||
# "fix" the usage and the description now that we know the subcommand.
|
||||
gen_usage(parser, args[0])
|
||||
return command(parser, args[1:])
|
||||
|
||||
# Not a known command. Default to help.
|
||||
gen_usage(parser, 'help')
|
||||
return CMDhelp(parser, args)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
fix_encoding.fix_encoding()
|
||||
sys.exit(main())
|
||||
60
git_cl.py
60
git_cl.py
@@ -463,9 +463,8 @@ or verify this branch is set up to track another (via the --track argument to
|
||||
def GetDescription(self, pretty=False):
|
||||
if not self.has_description:
|
||||
if self.GetIssue():
|
||||
path = '/' + self.GetIssue() + '/description'
|
||||
rpc_server = self.RpcServer()
|
||||
self.description = rpc_server.Send(path).strip()
|
||||
self.description = self.RpcServer().get_description(
|
||||
int(self.GetIssue())).strip()
|
||||
self.has_description = True
|
||||
if pretty:
|
||||
wrapper = textwrap.TextWrapper()
|
||||
@@ -494,10 +493,9 @@ or verify this branch is set up to track another (via the --track argument to
|
||||
self.has_patchset = False
|
||||
|
||||
def GetPatchSetDiff(self, issue):
|
||||
# Grab the last patchset of the issue first.
|
||||
data = json.loads(self.RpcServer().Send('/api/%s' % issue))
|
||||
patchset = data['patchsets'][-1]
|
||||
return self.RpcServer().Send(
|
||||
patchset = self.RpcServer().get_issue_properties(
|
||||
int(issue), False)['patchsets'][-1]
|
||||
return self.RpcServer().get(
|
||||
'/download/issue%s_%s.diff' % (issue, patchset))
|
||||
|
||||
def SetIssue(self, issue):
|
||||
@@ -564,20 +562,23 @@ or verify this branch is set up to track another (via the --track argument to
|
||||
return output
|
||||
|
||||
def CloseIssue(self):
|
||||
rpc_server = self.RpcServer()
|
||||
# Newer versions of Rietveld require us to pass an XSRF token to POST, so
|
||||
# we fetch it from the server. (The version used by Chromium has been
|
||||
# modified so the token isn't required when closing an issue.)
|
||||
xsrf_token = rpc_server.Send('/xsrf_token',
|
||||
extra_headers={'X-Requesting-XSRF-Token': '1'})
|
||||
return self.RpcServer().close_issue(int(self.GetIssue()))
|
||||
|
||||
# You cannot close an issue with a GET.
|
||||
# We pass an empty string for the data so it is a POST rather than a GET.
|
||||
data = [("description", self.description),
|
||||
("xsrf_token", xsrf_token)]
|
||||
ctype, body = upload.EncodeMultipartFormData(data, [])
|
||||
rpc_server.Send(
|
||||
'/' + self.GetIssue() + '/close', payload=body, content_type=ctype)
|
||||
def SetFlag(self, flag, value):
|
||||
"""Patchset must match."""
|
||||
if not self.GetPatchset():
|
||||
DieWithError('The patchset needs to match. Send another patchset.')
|
||||
try:
|
||||
return self.RpcServer().set_flag(
|
||||
int(self.GetIssue()), int(self.GetPatchset()), flag, value)
|
||||
except urllib2.HTTPError, e:
|
||||
if e.code == 404:
|
||||
DieWithError('The issue %s doesn\'t exist.' % self.GetIssue())
|
||||
if e.code == 403:
|
||||
DieWithError(
|
||||
('Access denied to issue %s. Maybe the patchset %s doesn\'t '
|
||||
'match?') % (self.GetIssue(), self.GetPatchset()))
|
||||
raise
|
||||
|
||||
def RpcServer(self):
|
||||
"""Returns an upload.RpcServer() to access this review's rietveld instance.
|
||||
@@ -913,6 +914,8 @@ def CMDupload(parser, args):
|
||||
dest="from_logs",
|
||||
help="""Squashes git commit logs into change description and
|
||||
uses message as subject""")
|
||||
parser.add_option('-c', '--use-commit-queue', action='store_true',
|
||||
help='tell the commit queue to commit this patchset')
|
||||
(options, args) = parser.parse_args(args)
|
||||
|
||||
# Make sure index is up-to-date before running diff-index.
|
||||
@@ -1021,6 +1024,9 @@ def CMDupload(parser, args):
|
||||
if not cl.GetIssue():
|
||||
cl.SetIssue(issue)
|
||||
cl.SetPatchset(patchset)
|
||||
|
||||
if options.use_commit_queue:
|
||||
cl.SetFlag('commit', '1')
|
||||
return 0
|
||||
|
||||
|
||||
@@ -1265,6 +1271,8 @@ def CMDpatch(parser, args):
|
||||
return 1
|
||||
issue_arg = args[0]
|
||||
|
||||
# TODO(maruel): Use apply_issue.py
|
||||
|
||||
if re.match(r'\d+', issue_arg):
|
||||
# Input is an issue id. Figure out the URL.
|
||||
issue = issue_arg
|
||||
@@ -1378,11 +1386,23 @@ def CMDtree(parser, args):
|
||||
def CMDupstream(parser, args):
|
||||
"""print the name of the upstream branch, if any"""
|
||||
_, args = parser.parse_args(args)
|
||||
if args:
|
||||
parser.error('Unrecognized args: %s' % ' '.join(args))
|
||||
cl = Changelist()
|
||||
print cl.GetUpstreamBranch()
|
||||
return 0
|
||||
|
||||
|
||||
def CMDset_commit(parser, args):
|
||||
"""set the commit bit"""
|
||||
_, args = parser.parse_args(args)
|
||||
if args:
|
||||
parser.error('Unrecognized args: %s' % ' '.join(args))
|
||||
cl = Changelist()
|
||||
cl.SetFlag('commit', '1')
|
||||
return 0
|
||||
|
||||
|
||||
def Command(name):
|
||||
return getattr(sys.modules[__name__], 'CMD' + name, None)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user