blob: dbd1b1394a14e044a19e322a2eb8e2de94e2824b [file] [log] [blame]
Mike Frysinger13f23a42013-05-13 17:32:01 -04001# Copyright (c) 2012 The Chromium OS Authors. All rights reserved.
2# Use of this source code is governed by a BSD-style license that can be
3# found in the LICENSE file.
4
Mike Frysinger08737512014-02-07 22:58:26 -05005"""A command line interface to Gerrit-on-borg instances.
Mike Frysinger13f23a42013-05-13 17:32:01 -04006
7Internal Note:
8To expose a function directly to the command line interface, name your function
9with the prefix "UserAct".
10"""
11
Mike Frysinger31ff6f92014-02-08 04:33:03 -050012from __future__ import print_function
13
Mike Frysinger13f23a42013-05-13 17:32:01 -040014import inspect
Yu-Ju Hongc20d7b32014-11-18 07:51:11 -080015import pprint
Vadim Bendeburydcfe2322013-05-23 10:54:49 -070016import re
Mike Frysinger13f23a42013-05-13 17:32:01 -040017
Don Garrett88b8d782014-05-13 17:30:55 -070018from chromite.cbuildbot import constants
Mike Frysinger13f23a42013-05-13 17:32:01 -040019from chromite.lib import commandline
20from chromite.lib import cros_build_lib
Ralph Nathan446aee92015-03-23 14:44:56 -070021from chromite.lib import cros_logging as logging
Mike Frysinger13f23a42013-05-13 17:32:01 -040022from chromite.lib import gerrit
Mathieu Olivari04b4d522014-12-18 17:26:34 -080023from chromite.lib import git
Mike Frysingerc85d8162014-02-08 00:45:21 -050024from chromite.lib import gob_util
Mike Frysinger13f23a42013-05-13 17:32:01 -040025from chromite.lib import terminal
26
27
Mike Frysinger031ad0b2013-05-14 18:15:34 -040028COLOR = None
Mike Frysinger13f23a42013-05-13 17:32:01 -040029
30# Map the internal names to the ones we normally show on the web ui.
31GERRIT_APPROVAL_MAP = {
Vadim Bendebury50571832013-11-12 10:43:19 -080032 'COMR': ['CQ', 'Commit Queue ',],
33 'CRVW': ['CR', 'Code Review ',],
34 'SUBM': ['S ', 'Submitted ',],
David James2b2e2c52014-12-02 19:32:07 -080035 'TRY': ['T ', 'Trybot Ready ',],
Vadim Bendebury50571832013-11-12 10:43:19 -080036 'VRIF': ['V ', 'Verified ',],
Mike Frysinger13f23a42013-05-13 17:32:01 -040037}
38
39# Order is important -- matches the web ui. This also controls the short
40# entries that we summarize in non-verbose mode.
41GERRIT_SUMMARY_CATS = ('CR', 'CQ', 'V',)
42
43
44def red(s):
45 return COLOR.Color(terminal.Color.RED, s)
46
47
48def green(s):
49 return COLOR.Color(terminal.Color.GREEN, s)
50
51
52def blue(s):
53 return COLOR.Color(terminal.Color.BLUE, s)
54
55
56def limits(cls):
57 """Given a dict of fields, calculate the longest string lengths
58
59 This allows you to easily format the output of many results so that the
60 various cols all line up correctly.
61 """
62 lims = {}
63 for cl in cls:
64 for k in cl.keys():
Mike Frysingerf16b8f02013-10-21 22:24:46 -040065 # Use %s rather than str() to avoid codec issues.
66 # We also do this so we can format integers.
67 lims[k] = max(lims.get(k, 0), len('%s' % cl[k]))
Mike Frysinger13f23a42013-05-13 17:32:01 -040068 return lims
69
70
Mike Frysinger88f27292014-06-17 09:40:45 -070071# TODO: This func really needs to be merged into the core gerrit logic.
72def GetGerrit(opts, cl=None):
73 """Auto pick the right gerrit instance based on the |cl|
74
75 Args:
76 opts: The general options object.
77 cl: A CL taking one of the forms: 1234 *1234 chromium:1234
78
79 Returns:
80 A tuple of a gerrit object and a sanitized CL #.
81 """
82 gob = opts.gob
83 if not cl is None:
84 if cl.startswith('*'):
85 gob = constants.INTERNAL_GOB_INSTANCE
86 cl = cl[1:]
87 elif ':' in cl:
88 gob, cl = cl.split(':', 1)
89
90 if not gob in opts.gerrit:
91 opts.gerrit[gob] = gerrit.GetGerritHelper(gob=gob, print_cmd=opts.debug)
92
93 return (opts.gerrit[gob], cl)
94
95
Mike Frysinger13f23a42013-05-13 17:32:01 -040096def GetApprovalSummary(_opts, cls):
97 """Return a dict of the most important approvals"""
98 approvs = dict([(x, '') for x in GERRIT_SUMMARY_CATS])
99 if 'approvals' in cls['currentPatchSet']:
100 for approver in cls['currentPatchSet']['approvals']:
101 cats = GERRIT_APPROVAL_MAP.get(approver['type'])
102 if not cats:
Ralph Nathan446aee92015-03-23 14:44:56 -0700103 logging.warning('unknown gerrit approval type: %s', approver['type'])
Mike Frysinger13f23a42013-05-13 17:32:01 -0400104 continue
105 cat = cats[0].strip()
106 val = int(approver['value'])
107 if not cat in approvs:
108 # Ignore the extended categories in the summary view.
109 continue
110 elif approvs[cat] is '':
111 approvs[cat] = val
112 elif val < 0:
113 approvs[cat] = min(approvs[cat], val)
114 else:
115 approvs[cat] = max(approvs[cat], val)
116 return approvs
117
118
119def PrintCl(opts, cls, lims, show_approvals=True):
120 """Pretty print a single result"""
Mike Frysingerf70bdc72014-06-15 00:44:06 -0700121 if opts.raw:
122 # Special case internal Chrome GoB as that is what most devs use.
123 # They can always redirect the list elsewhere via the -g option.
124 if opts.gob == constants.INTERNAL_GOB_INSTANCE:
125 print(constants.INTERNAL_CHANGE_PREFIX, end='')
126 print(cls['number'])
127 return
128
Mike Frysinger13f23a42013-05-13 17:32:01 -0400129 if not lims:
130 lims = {'url': 0, 'project': 0}
131
132 status = ''
133 if show_approvals and not opts.verbose:
134 approvs = GetApprovalSummary(opts, cls)
135 for cat in GERRIT_SUMMARY_CATS:
136 if approvs[cat] is '':
137 functor = lambda x: x
138 elif approvs[cat] < 0:
139 functor = red
140 else:
141 functor = green
142 status += functor('%s:%2s ' % (cat, approvs[cat]))
143
Mike Frysinger31ff6f92014-02-08 04:33:03 -0500144 print('%s %s%-*s %s' % (blue('%-*s' % (lims['url'], cls['url'])), status,
145 lims['project'], cls['project'], cls['subject']))
Mike Frysinger13f23a42013-05-13 17:32:01 -0400146
147 if show_approvals and opts.verbose:
148 for approver in cls['currentPatchSet'].get('approvals', []):
149 functor = red if int(approver['value']) < 0 else green
150 n = functor('%2s' % approver['value'])
151 t = GERRIT_APPROVAL_MAP.get(approver['type'], [approver['type'],
152 approver['type']])[1]
Mike Frysinger31ff6f92014-02-08 04:33:03 -0500153 print(' %s %s %s' % (n, t, approver['by']['email']))
Mike Frysinger13f23a42013-05-13 17:32:01 -0400154
155
156def _MyUserInfo():
Mathieu Olivari04b4d522014-12-18 17:26:34 -0800157 email = git.GetProjectUserEmail(constants.CHROMITE_DIR)
158 [username, _, domain] = email.partition('@')
159 if domain in ('google.com', 'chromium.org'):
160 emails = ['%s@%s' % (username, domain)
161 for domain in ('google.com', 'chromium.org')]
162 else:
163 emails = [email]
Mike Frysinger13f23a42013-05-13 17:32:01 -0400164 reviewers = ['reviewer:%s' % x for x in emails]
165 owners = ['owner:%s' % x for x in emails]
166 return emails, reviewers, owners
167
168
169def FilteredQuery(opts, query):
170 """Query gerrit and filter/clean up the results"""
171 ret = []
172
Vadim Bendebury6e057b32014-12-29 09:41:36 -0800173 if opts.branch is not None:
174 query += ' branch:%s' % opts.branch
Mathieu Olivariedc45b82015-01-12 19:43:20 -0800175 if opts.project is not None:
176 query += ' project: %s' % opts.project
Mathieu Olivari14645a12015-01-16 15:41:32 -0800177 if opts.topic is not None:
178 query += ' topic: %s' % opts.topic
Vadim Bendebury6e057b32014-12-29 09:41:36 -0800179
Mike Frysinger88f27292014-06-17 09:40:45 -0700180 helper, _ = GetGerrit(opts)
181 for cl in helper.Query(query, raw=True, bypass_cache=False):
Mike Frysinger13f23a42013-05-13 17:32:01 -0400182 # Gerrit likes to return a stats record too.
183 if not 'project' in cl:
184 continue
185
186 # Strip off common leading names since the result is still
187 # unique over the whole tree.
188 if not opts.verbose:
Mike Frysingere5e78272014-06-15 00:41:30 -0700189 for pfx in ('chromeos', 'chromiumos', 'overlays', 'platform',
190 'third_party'):
Mike Frysinger13f23a42013-05-13 17:32:01 -0400191 if cl['project'].startswith('%s/' % pfx):
192 cl['project'] = cl['project'][len(pfx) + 1:]
193
194 ret.append(cl)
195
196 if opts.sort in ('number',):
197 key = lambda x: int(x[opts.sort])
198 else:
199 key = lambda x: x[opts.sort]
200 return sorted(ret, key=key)
201
202
Mike Frysinger13f23a42013-05-13 17:32:01 -0400203def IsApprover(cl, users):
204 """See if the approvers in |cl| is listed in |users|"""
205 # See if we are listed in the approvals list. We have to parse
206 # this by hand as the gerrit query system doesn't support it :(
207 # http://code.google.com/p/gerrit/issues/detail?id=1235
208 if 'approvals' not in cl['currentPatchSet']:
209 return False
210
211 if isinstance(users, basestring):
212 users = (users,)
213
214 for approver in cl['currentPatchSet']['approvals']:
Stefan Zager29560302013-09-06 14:30:54 -0700215 if (approver['by']['email'] in users and
216 approver['type'] == 'CRVW' and
217 int(approver['value']) != 0):
Mike Frysinger13f23a42013-05-13 17:32:01 -0400218 return True
219
220 return False
221
222
223def UserActTodo(opts):
224 """List CLs needing your review"""
225 emails, reviewers, owners = _MyUserInfo()
Mike Frysingerd6e2df02014-11-26 02:55:04 -0500226 cls = FilteredQuery(opts, ('( %s ) status:open NOT ( %s )' %
227 (' OR '.join(reviewers), ' OR '.join(owners))))
Mike Frysinger13f23a42013-05-13 17:32:01 -0400228 cls = [x for x in cls if not IsApprover(x, emails)]
229 lims = limits(cls)
230 for cl in cls:
231 PrintCl(opts, cl, lims)
232
233
Mike Frysingera1db2c42014-06-15 00:42:48 -0700234def UserActSearch(opts, query):
235 """List CLs matching the Gerrit <search query>"""
236 cls = FilteredQuery(opts, query)
Mike Frysinger13f23a42013-05-13 17:32:01 -0400237 lims = limits(cls)
238 for cl in cls:
239 PrintCl(opts, cl, lims)
240
241
Mike Frysingera1db2c42014-06-15 00:42:48 -0700242def UserActMine(opts):
243 """List your CLs with review statuses"""
244 _, _, owners = _MyUserInfo()
245 UserActSearch(opts, '( %s ) status:new' % (' OR '.join(owners),))
246
247
Mike Frysingerd8f841c2014-06-15 00:48:26 -0700248def UserActInspect(opts, *args):
249 """Inspect CL number <n> [n ...]"""
Mike Frysinger88f27292014-06-17 09:40:45 -0700250 for arg in args:
251 cl = FilteredQuery(opts, arg)
Mike Frysingerd8f841c2014-06-15 00:48:26 -0700252 if cl:
253 PrintCl(opts, cl[0], None)
254 else:
Mike Frysinger88f27292014-06-17 09:40:45 -0700255 print('no results found for CL %s' % arg)
Mike Frysinger13f23a42013-05-13 17:32:01 -0400256
257
Mike Frysingerd8f841c2014-06-15 00:48:26 -0700258def UserActReview(opts, *args):
259 """Mark CL <n> [n ...] with code review status <-2,-1,0,1,2>"""
260 num = args[-1]
Mike Frysinger88f27292014-06-17 09:40:45 -0700261 for arg in args[:-1]:
262 helper, cl = GetGerrit(opts, arg)
263 helper.SetReview(cl, labels={'Code-Review': num}, dryrun=opts.dryrun)
Mike Frysingerd8f841c2014-06-15 00:48:26 -0700264UserActReview.arg_min = 2
Mike Frysinger13f23a42013-05-13 17:32:01 -0400265
266
Mike Frysingerd8f841c2014-06-15 00:48:26 -0700267def UserActVerify(opts, *args):
268 """Mark CL <n> [n ...] with verify status <-1,0,1>"""
269 num = args[-1]
Mike Frysinger88f27292014-06-17 09:40:45 -0700270 for arg in args[:-1]:
271 helper, cl = GetGerrit(opts, arg)
272 helper.SetReview(cl, labels={'Verified': num}, dryrun=opts.dryrun)
Mike Frysingerd8f841c2014-06-15 00:48:26 -0700273UserActVerify.arg_min = 2
Mike Frysinger13f23a42013-05-13 17:32:01 -0400274
275
Mike Frysingerd8f841c2014-06-15 00:48:26 -0700276def UserActReady(opts, *args):
277 """Mark CL <n> [n ...] with ready status <0,1,2>"""
278 num = args[-1]
Mike Frysinger88f27292014-06-17 09:40:45 -0700279 for arg in args[:-1]:
280 helper, cl = GetGerrit(opts, arg)
281 helper.SetReview(cl, labels={'Commit-Queue': num}, dryrun=opts.dryrun)
Mike Frysingerd8f841c2014-06-15 00:48:26 -0700282UserActReady.arg_min = 2
Mike Frysinger13f23a42013-05-13 17:32:01 -0400283
284
Mike Frysinger15b23e42014-12-05 17:00:05 -0500285def UserActTrybotready(opts, *args):
286 """Mark CL <n> [n ...] with trybot-ready status <0,1>"""
287 num = args[-1]
288 for arg in args[:-1]:
289 helper, cl = GetGerrit(opts, arg)
290 helper.SetReview(cl, labels={'Trybot-Ready': num}, dryrun=opts.dryrun)
291UserActTrybotready.arg_min = 2
292
293
Mike Frysingerd8f841c2014-06-15 00:48:26 -0700294def UserActSubmit(opts, *args):
295 """Submit CL <n> [n ...]"""
Mike Frysinger88f27292014-06-17 09:40:45 -0700296 for arg in args:
297 helper, cl = GetGerrit(opts, arg)
298 helper.SubmitChange(cl, dryrun=opts.dryrun)
Mike Frysinger13f23a42013-05-13 17:32:01 -0400299
300
Mike Frysingerd8f841c2014-06-15 00:48:26 -0700301def UserActAbandon(opts, *args):
302 """Abandon CL <n> [n ...]"""
Mike Frysinger88f27292014-06-17 09:40:45 -0700303 for arg in args:
304 helper, cl = GetGerrit(opts, arg)
305 helper.AbandonChange(cl, dryrun=opts.dryrun)
Mike Frysinger13f23a42013-05-13 17:32:01 -0400306
307
Mike Frysingerd8f841c2014-06-15 00:48:26 -0700308def UserActRestore(opts, *args):
309 """Restore CL <n> [n ...] that was abandoned"""
Mike Frysinger88f27292014-06-17 09:40:45 -0700310 for arg in args:
311 helper, cl = GetGerrit(opts, arg)
312 helper.RestoreChange(cl, dryrun=opts.dryrun)
Mike Frysinger13f23a42013-05-13 17:32:01 -0400313
314
Mike Frysinger88f27292014-06-17 09:40:45 -0700315def UserActReviewers(opts, cl, *args):
Vadim Bendeburydcfe2322013-05-23 10:54:49 -0700316 """Add/remove reviewers' emails for CL <n> (prepend with '~' to remove)"""
Mike Frysingerc15efa52013-12-12 01:13:56 -0500317 emails = args
Vadim Bendeburydcfe2322013-05-23 10:54:49 -0700318 # Allow for optional leading '~'.
319 email_validator = re.compile(r'^[~]?%s$' % constants.EMAIL_REGEX)
320 add_list, remove_list, invalid_list = [], [], []
321
322 for x in emails:
323 if not email_validator.match(x):
324 invalid_list.append(x)
325 elif x[0] == '~':
326 remove_list.append(x[1:])
327 else:
328 add_list.append(x)
329
330 if invalid_list:
331 cros_build_lib.Die(
332 'Invalid email address(es): %s' % ', '.join(invalid_list))
333
334 if add_list or remove_list:
Mike Frysinger88f27292014-06-17 09:40:45 -0700335 helper, cl = GetGerrit(opts, cl)
336 helper.SetReviewers(cl, add=add_list, remove=remove_list,
337 dryrun=opts.dryrun)
Vadim Bendeburydcfe2322013-05-23 10:54:49 -0700338
339
Mike Frysinger88f27292014-06-17 09:40:45 -0700340def UserActMessage(opts, cl, message):
Doug Anderson8119df02013-07-20 21:00:24 +0530341 """Add a message to CL <n>"""
Mike Frysinger88f27292014-06-17 09:40:45 -0700342 helper, cl = GetGerrit(opts, cl)
343 helper.SetReview(cl, msg=message, dryrun=opts.dryrun)
Doug Anderson8119df02013-07-20 21:00:24 +0530344
345
Mathieu Olivari02f89b32015-01-09 13:53:38 -0800346def UserActTopic(opts, topic, *args):
347 """Set |topic| for CL number <n> [n ...]"""
348 for arg in args:
349 helper, arg = GetGerrit(opts, arg)
350 helper.SetTopic(arg, topic, dryrun=opts.dryrun)
351
352
Mike Frysingerd8f841c2014-06-15 00:48:26 -0700353def UserActDeletedraft(opts, *args):
354 """Delete draft patch set <n> [n ...]"""
Mike Frysinger88f27292014-06-17 09:40:45 -0700355 for arg in args:
356 helper, cl = GetGerrit(opts, arg)
357 helper.DeleteDraft(cl, dryrun=opts.dryrun)
Jon Salza427fb02014-03-07 18:13:17 +0800358
359
Yu-Ju Hongc20d7b32014-11-18 07:51:11 -0800360def UserActAccount(opts):
361 """Get user account information."""
362 helper, _ = GetGerrit(opts)
363 pprint.PrettyPrinter().pprint(helper.GetAccount())
364
365
Mike Frysinger13f23a42013-05-13 17:32:01 -0400366def main(argv):
367 # Locate actions that are exposed to the user. All functions that start
368 # with "UserAct" are fair game.
369 act_pfx = 'UserAct'
370 actions = [x for x in globals() if x.startswith(act_pfx)]
371
Mike Frysingerddf86eb2014-02-07 22:51:41 -0500372 usage = """%(prog)s [options] <action> [action args]
Mike Frysinger13f23a42013-05-13 17:32:01 -0400373
374There is no support for doing line-by-line code review via the command line.
375This helps you manage various bits and CL status.
376
Mike Frysingera1db2c42014-06-15 00:42:48 -0700377For general Gerrit documentation, see:
378 https://gerrit-review.googlesource.com/Documentation/
379The Searching Changes page covers the search query syntax:
380 https://gerrit-review.googlesource.com/Documentation/user-search.html
381
Mike Frysinger13f23a42013-05-13 17:32:01 -0400382Example:
383 $ gerrit todo # List all the CLs that await your review.
384 $ gerrit mine # List all of your open CLs.
385 $ gerrit inspect 28123 # Inspect CL 28123 on the public gerrit.
386 $ gerrit inspect *28123 # Inspect CL 28123 on the internal gerrit.
387 $ gerrit verify 28123 1 # Mark CL 28123 as verified (+1).
Mike Frysingerd8f841c2014-06-15 00:48:26 -0700388Scripting:
Mike Frysinger88f27292014-06-17 09:40:45 -0700389 $ gerrit ready `gerrit --raw mine` 1 # Mark *ALL* of your public CLs \
390ready.
391 $ gerrit ready `gerrit --raw -i mine` 1 # Mark *ALL* of your internal CLs \
392ready.
Mike Frysinger13f23a42013-05-13 17:32:01 -0400393
394Actions:"""
395 indent = max([len(x) - len(act_pfx) for x in actions])
396 for a in sorted(actions):
Mike Frysinger15b23e42014-12-05 17:00:05 -0500397 cmd = a[len(act_pfx):]
398 # Sanity check for devs adding new commands. Should be quick.
399 if cmd != cmd.lower().capitalize():
400 raise RuntimeError('callback "%s" is misnamed; should be "%s"' %
401 (cmd, cmd.lower().capitalize()))
402 usage += '\n %-*s: %s' % (indent, cmd.lower(), globals()[a].__doc__)
Mike Frysinger13f23a42013-05-13 17:32:01 -0400403
Mike Frysingerddf86eb2014-02-07 22:51:41 -0500404 parser = commandline.ArgumentParser(usage=usage)
Mike Frysinger08737512014-02-07 22:58:26 -0500405 parser.add_argument('-i', '--internal', dest='gob', action='store_const',
Mike Frysinger88f27292014-06-17 09:40:45 -0700406 default=constants.EXTERNAL_GOB_INSTANCE,
Mike Frysinger40541c62014-02-08 04:38:37 -0500407 const=constants.INTERNAL_GOB_INSTANCE,
Mike Frysinger08737512014-02-07 22:58:26 -0500408 help='Query internal Chromium Gerrit instance')
409 parser.add_argument('-g', '--gob',
Mike Frysinger88f27292014-06-17 09:40:45 -0700410 default=constants.EXTERNAL_GOB_INSTANCE,
Mike Frysingerd6e2df02014-11-26 02:55:04 -0500411 help=('Gerrit (on borg) instance to query (default: %s)' %
412 (constants.EXTERNAL_GOB_INSTANCE)))
Mike Frysingerddf86eb2014-02-07 22:51:41 -0500413 parser.add_argument('--sort', default='number',
414 help='Key to sort on (number, project)')
Mike Frysingerf70bdc72014-06-15 00:44:06 -0700415 parser.add_argument('--raw', default=False, action='store_true',
416 help='Return raw results (suitable for scripting)')
Mike Frysinger550d9aa2014-06-15 00:55:31 -0700417 parser.add_argument('-n', '--dry-run', default=False, action='store_true',
418 dest='dryrun',
419 help='Show what would be done, but do not make changes')
Mike Frysingerddf86eb2014-02-07 22:51:41 -0500420 parser.add_argument('-v', '--verbose', default=False, action='store_true',
421 help='Be more verbose in output')
Vadim Bendebury6e057b32014-12-29 09:41:36 -0800422 parser.add_argument('-b', '--branch',
423 help='Limit output to the specific branch')
Mathieu Olivariedc45b82015-01-12 19:43:20 -0800424 parser.add_argument('-p', '--project',
425 help='Limit output to the specific project')
Mathieu Olivari14645a12015-01-16 15:41:32 -0800426 parser.add_argument('-t', '--topic',
427 help='Limit output to the specific topic')
Mike Frysingerddf86eb2014-02-07 22:51:41 -0500428 parser.add_argument('args', nargs='+')
429 opts = parser.parse_args(argv)
Mike Frysinger13f23a42013-05-13 17:32:01 -0400430
Mike Frysinger88f27292014-06-17 09:40:45 -0700431 # A cache of gerrit helpers we'll load on demand.
432 opts.gerrit = {}
433 opts.Freeze()
434
Mike Frysinger031ad0b2013-05-14 18:15:34 -0400435 # pylint: disable=W0603
436 global COLOR
437 COLOR = terminal.Color(enabled=opts.color)
438
Mike Frysinger13f23a42013-05-13 17:32:01 -0400439 # Now look up the requested user action and run it.
Mike Frysinger88f27292014-06-17 09:40:45 -0700440 cmd = opts.args[0].lower()
441 args = opts.args[1:]
Mike Frysinger13f23a42013-05-13 17:32:01 -0400442 functor = globals().get(act_pfx + cmd.capitalize())
443 if functor:
444 argspec = inspect.getargspec(functor)
Vadim Bendeburydcfe2322013-05-23 10:54:49 -0700445 if argspec.varargs:
Mike Frysingerd8f841c2014-06-15 00:48:26 -0700446 arg_min = getattr(functor, 'arg_min', len(argspec.args))
447 if len(args) < arg_min:
Vadim Bendeburydcfe2322013-05-23 10:54:49 -0700448 parser.error('incorrect number of args: %s expects at least %s' %
Mike Frysingerd8f841c2014-06-15 00:48:26 -0700449 (cmd, arg_min))
Vadim Bendeburydcfe2322013-05-23 10:54:49 -0700450 elif len(argspec.args) - 1 != len(args):
Mike Frysinger13f23a42013-05-13 17:32:01 -0400451 parser.error('incorrect number of args: %s expects %s' %
452 (cmd, len(argspec.args) - 1))
Vadim Bendebury614f8682013-05-23 10:33:35 -0700453 try:
454 functor(opts, *args)
Mike Frysingerc85d8162014-02-08 00:45:21 -0500455 except (cros_build_lib.RunCommandError, gerrit.GerritException,
456 gob_util.GOBError) as e:
Vadim Bendeburydcfe2322013-05-23 10:54:49 -0700457 cros_build_lib.Die(e.message)
Mike Frysinger13f23a42013-05-13 17:32:01 -0400458 else:
459 parser.error('unknown action: %s' % (cmd,))