commit_queue.py 5.02 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 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())