blob: ae97e4902af4f5839bbff9c9ce1f645334ed4609 [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
Paul Hobbs89765232015-06-24 14:07:49 -070083 if cl is not None:
Mike Frysinger88f27292014-06-17 09:40:45 -070084 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
Paul Hobbs89765232015-06-24 14:07:49 -0700169def _Query(opts, query, raw=True):
170 """Queries Gerrit with a query string built from the commandline options"""
Vadim Bendebury6e057b32014-12-29 09:41:36 -0800171 if opts.branch is not None:
172 query += ' branch:%s' % opts.branch
Mathieu Olivariedc45b82015-01-12 19:43:20 -0800173 if opts.project is not None:
174 query += ' project: %s' % opts.project
Mathieu Olivari14645a12015-01-16 15:41:32 -0800175 if opts.topic is not None:
176 query += ' topic: %s' % opts.topic
Vadim Bendebury6e057b32014-12-29 09:41:36 -0800177
Mike Frysinger88f27292014-06-17 09:40:45 -0700178 helper, _ = GetGerrit(opts)
Paul Hobbs89765232015-06-24 14:07:49 -0700179 return helper.Query(query, raw=raw, bypass_cache=False)
180
181
182def FilteredQuery(opts, query):
183 """Query gerrit and filter/clean up the results"""
184 ret = []
185
186 for cl in _Query(opts, query, raw=True):
Mike Frysinger13f23a42013-05-13 17:32:01 -0400187 # Gerrit likes to return a stats record too.
188 if not 'project' in cl:
189 continue
190
191 # Strip off common leading names since the result is still
192 # unique over the whole tree.
193 if not opts.verbose:
Mike Frysingere5e78272014-06-15 00:41:30 -0700194 for pfx in ('chromeos', 'chromiumos', 'overlays', 'platform',
195 'third_party'):
Mike Frysinger13f23a42013-05-13 17:32:01 -0400196 if cl['project'].startswith('%s/' % pfx):
197 cl['project'] = cl['project'][len(pfx) + 1:]
198
199 ret.append(cl)
200
Paul Hobbs89765232015-06-24 14:07:49 -0700201 if opts.sort == 'number':
Mike Frysinger13f23a42013-05-13 17:32:01 -0400202 key = lambda x: int(x[opts.sort])
203 else:
204 key = lambda x: x[opts.sort]
205 return sorted(ret, key=key)
206
207
Mike Frysinger13f23a42013-05-13 17:32:01 -0400208def IsApprover(cl, users):
209 """See if the approvers in |cl| is listed in |users|"""
210 # See if we are listed in the approvals list. We have to parse
211 # this by hand as the gerrit query system doesn't support it :(
212 # http://code.google.com/p/gerrit/issues/detail?id=1235
213 if 'approvals' not in cl['currentPatchSet']:
214 return False
215
216 if isinstance(users, basestring):
217 users = (users,)
218
219 for approver in cl['currentPatchSet']['approvals']:
Stefan Zager29560302013-09-06 14:30:54 -0700220 if (approver['by']['email'] in users and
221 approver['type'] == 'CRVW' and
222 int(approver['value']) != 0):
Mike Frysinger13f23a42013-05-13 17:32:01 -0400223 return True
224
225 return False
226
227
228def UserActTodo(opts):
229 """List CLs needing your review"""
230 emails, reviewers, owners = _MyUserInfo()
Mike Frysingerd6e2df02014-11-26 02:55:04 -0500231 cls = FilteredQuery(opts, ('( %s ) status:open NOT ( %s )' %
232 (' OR '.join(reviewers), ' OR '.join(owners))))
Mike Frysinger13f23a42013-05-13 17:32:01 -0400233 cls = [x for x in cls if not IsApprover(x, emails)]
234 lims = limits(cls)
235 for cl in cls:
236 PrintCl(opts, cl, lims)
237
238
Mike Frysingera1db2c42014-06-15 00:42:48 -0700239def UserActSearch(opts, query):
240 """List CLs matching the Gerrit <search query>"""
241 cls = FilteredQuery(opts, query)
Mike Frysinger13f23a42013-05-13 17:32:01 -0400242 lims = limits(cls)
243 for cl in cls:
244 PrintCl(opts, cl, lims)
245
246
Mike Frysingera1db2c42014-06-15 00:42:48 -0700247def UserActMine(opts):
248 """List your CLs with review statuses"""
249 _, _, owners = _MyUserInfo()
250 UserActSearch(opts, '( %s ) status:new' % (' OR '.join(owners),))
251
252
Paul Hobbs89765232015-06-24 14:07:49 -0700253def _BreadthFirstSearch(to_visit, children, visited_key=lambda x: x):
254 """Runs breadth first search starting from the nodes in |to_visit|
255
256 Args:
257 to_visit: the starting nodes
258 children: a function which takes a node and returns the nodes adjacent to it
259 visited_key: a function for deduplicating node visits. Defaults to the
260 identity function (lambda x: x)
261
262 Returns:
263 A list of nodes which are reachable from any node in |to_visit| by calling
264 |children| any number of times.
265 """
266 to_visit = list(to_visit)
267 seen = set(map(visited_key, to_visit))
268 for node in to_visit:
269 for child in children(node):
270 key = visited_key(child)
271 if key not in seen:
272 seen.add(key)
273 to_visit.append(child)
274 return to_visit
275
276
277def UserActDeps(opts, query):
278 """List CLs matching a query, and all transitive dependencies of those CLs"""
279 cls = _Query(opts, query, raw=False)
280
281 @cros_build_lib.Memoize
282 def _QueryChange(cl):
283 return _Query(opts, cl, raw=False)
284
285 def _Children(cl):
286 """Returns the Gerrit and CQ-Depends dependencies of a patch"""
287 cq_deps = cl.PaladinDependencies(None)
288 direct_deps = cl.GerritDependencies() + cq_deps
289 # We need to query the change to guarantee that we have a .gerrit_number
290 for dep in direct_deps:
291 # TODO(phobbs) this should maybe catch network errors.
292 change = _QueryChange(dep.ToGerritQueryText())[-1]
293 if change.status == 'NEW':
294 yield change
295
296 transitives = _BreadthFirstSearch(
297 cls, _Children,
298 visited_key=lambda cl: cl.gerrit_number)
299
300 transitives_raw = [cl.patch_dict for cl in transitives]
301 lims = limits(transitives_raw)
302 for cl in transitives_raw:
303 PrintCl(opts, cl, lims)
304
305
Mike Frysingerd8f841c2014-06-15 00:48:26 -0700306def UserActInspect(opts, *args):
307 """Inspect CL number <n> [n ...]"""
Mike Frysinger88f27292014-06-17 09:40:45 -0700308 for arg in args:
309 cl = FilteredQuery(opts, arg)
Mike Frysingerd8f841c2014-06-15 00:48:26 -0700310 if cl:
311 PrintCl(opts, cl[0], None)
312 else:
Mike Frysinger88f27292014-06-17 09:40:45 -0700313 print('no results found for CL %s' % arg)
Mike Frysinger13f23a42013-05-13 17:32:01 -0400314
315
Mike Frysingerd8f841c2014-06-15 00:48:26 -0700316def UserActReview(opts, *args):
317 """Mark CL <n> [n ...] with code review status <-2,-1,0,1,2>"""
318 num = args[-1]
Mike Frysinger88f27292014-06-17 09:40:45 -0700319 for arg in args[:-1]:
320 helper, cl = GetGerrit(opts, arg)
321 helper.SetReview(cl, labels={'Code-Review': num}, dryrun=opts.dryrun)
Mike Frysingerd8f841c2014-06-15 00:48:26 -0700322UserActReview.arg_min = 2
Mike Frysinger13f23a42013-05-13 17:32:01 -0400323
324
Mike Frysingerd8f841c2014-06-15 00:48:26 -0700325def UserActVerify(opts, *args):
326 """Mark CL <n> [n ...] with verify status <-1,0,1>"""
327 num = args[-1]
Mike Frysinger88f27292014-06-17 09:40:45 -0700328 for arg in args[:-1]:
329 helper, cl = GetGerrit(opts, arg)
330 helper.SetReview(cl, labels={'Verified': num}, dryrun=opts.dryrun)
Mike Frysingerd8f841c2014-06-15 00:48:26 -0700331UserActVerify.arg_min = 2
Mike Frysinger13f23a42013-05-13 17:32:01 -0400332
333
Mike Frysingerd8f841c2014-06-15 00:48:26 -0700334def UserActReady(opts, *args):
335 """Mark CL <n> [n ...] with ready status <0,1,2>"""
336 num = args[-1]
Mike Frysinger88f27292014-06-17 09:40:45 -0700337 for arg in args[:-1]:
338 helper, cl = GetGerrit(opts, arg)
339 helper.SetReview(cl, labels={'Commit-Queue': num}, dryrun=opts.dryrun)
Mike Frysingerd8f841c2014-06-15 00:48:26 -0700340UserActReady.arg_min = 2
Mike Frysinger13f23a42013-05-13 17:32:01 -0400341
342
Mike Frysinger15b23e42014-12-05 17:00:05 -0500343def UserActTrybotready(opts, *args):
344 """Mark CL <n> [n ...] with trybot-ready status <0,1>"""
345 num = args[-1]
346 for arg in args[:-1]:
347 helper, cl = GetGerrit(opts, arg)
348 helper.SetReview(cl, labels={'Trybot-Ready': num}, dryrun=opts.dryrun)
349UserActTrybotready.arg_min = 2
350
351
Mike Frysingerd8f841c2014-06-15 00:48:26 -0700352def UserActSubmit(opts, *args):
353 """Submit CL <n> [n ...]"""
Mike Frysinger88f27292014-06-17 09:40:45 -0700354 for arg in args:
355 helper, cl = GetGerrit(opts, arg)
356 helper.SubmitChange(cl, dryrun=opts.dryrun)
Mike Frysinger13f23a42013-05-13 17:32:01 -0400357
358
Mike Frysingerd8f841c2014-06-15 00:48:26 -0700359def UserActAbandon(opts, *args):
360 """Abandon CL <n> [n ...]"""
Mike Frysinger88f27292014-06-17 09:40:45 -0700361 for arg in args:
362 helper, cl = GetGerrit(opts, arg)
363 helper.AbandonChange(cl, dryrun=opts.dryrun)
Mike Frysinger13f23a42013-05-13 17:32:01 -0400364
365
Mike Frysingerd8f841c2014-06-15 00:48:26 -0700366def UserActRestore(opts, *args):
367 """Restore CL <n> [n ...] that was abandoned"""
Mike Frysinger88f27292014-06-17 09:40:45 -0700368 for arg in args:
369 helper, cl = GetGerrit(opts, arg)
370 helper.RestoreChange(cl, dryrun=opts.dryrun)
Mike Frysinger13f23a42013-05-13 17:32:01 -0400371
372
Mike Frysinger88f27292014-06-17 09:40:45 -0700373def UserActReviewers(opts, cl, *args):
Vadim Bendeburydcfe2322013-05-23 10:54:49 -0700374 """Add/remove reviewers' emails for CL <n> (prepend with '~' to remove)"""
Mike Frysingerc15efa52013-12-12 01:13:56 -0500375 emails = args
Vadim Bendeburydcfe2322013-05-23 10:54:49 -0700376 # Allow for optional leading '~'.
377 email_validator = re.compile(r'^[~]?%s$' % constants.EMAIL_REGEX)
378 add_list, remove_list, invalid_list = [], [], []
379
380 for x in emails:
381 if not email_validator.match(x):
382 invalid_list.append(x)
383 elif x[0] == '~':
384 remove_list.append(x[1:])
385 else:
386 add_list.append(x)
387
388 if invalid_list:
389 cros_build_lib.Die(
390 'Invalid email address(es): %s' % ', '.join(invalid_list))
391
392 if add_list or remove_list:
Mike Frysinger88f27292014-06-17 09:40:45 -0700393 helper, cl = GetGerrit(opts, cl)
394 helper.SetReviewers(cl, add=add_list, remove=remove_list,
395 dryrun=opts.dryrun)
Vadim Bendeburydcfe2322013-05-23 10:54:49 -0700396
397
Mike Frysinger88f27292014-06-17 09:40:45 -0700398def UserActMessage(opts, cl, message):
Doug Anderson8119df02013-07-20 21:00:24 +0530399 """Add a message to CL <n>"""
Mike Frysinger88f27292014-06-17 09:40:45 -0700400 helper, cl = GetGerrit(opts, cl)
401 helper.SetReview(cl, msg=message, dryrun=opts.dryrun)
Doug Anderson8119df02013-07-20 21:00:24 +0530402
403
Mathieu Olivari02f89b32015-01-09 13:53:38 -0800404def UserActTopic(opts, topic, *args):
405 """Set |topic| for CL number <n> [n ...]"""
406 for arg in args:
407 helper, arg = GetGerrit(opts, arg)
408 helper.SetTopic(arg, topic, dryrun=opts.dryrun)
409
410
Mike Frysingerd8f841c2014-06-15 00:48:26 -0700411def UserActDeletedraft(opts, *args):
412 """Delete draft patch set <n> [n ...]"""
Mike Frysinger88f27292014-06-17 09:40:45 -0700413 for arg in args:
414 helper, cl = GetGerrit(opts, arg)
415 helper.DeleteDraft(cl, dryrun=opts.dryrun)
Jon Salza427fb02014-03-07 18:13:17 +0800416
417
Yu-Ju Hongc20d7b32014-11-18 07:51:11 -0800418def UserActAccount(opts):
419 """Get user account information."""
420 helper, _ = GetGerrit(opts)
421 pprint.PrettyPrinter().pprint(helper.GetAccount())
422
423
Mike Frysinger13f23a42013-05-13 17:32:01 -0400424def main(argv):
425 # Locate actions that are exposed to the user. All functions that start
426 # with "UserAct" are fair game.
427 act_pfx = 'UserAct'
428 actions = [x for x in globals() if x.startswith(act_pfx)]
429
Mike Frysingerddf86eb2014-02-07 22:51:41 -0500430 usage = """%(prog)s [options] <action> [action args]
Mike Frysinger13f23a42013-05-13 17:32:01 -0400431
432There is no support for doing line-by-line code review via the command line.
433This helps you manage various bits and CL status.
434
Mike Frysingera1db2c42014-06-15 00:42:48 -0700435For general Gerrit documentation, see:
436 https://gerrit-review.googlesource.com/Documentation/
437The Searching Changes page covers the search query syntax:
438 https://gerrit-review.googlesource.com/Documentation/user-search.html
439
Mike Frysinger13f23a42013-05-13 17:32:01 -0400440Example:
441 $ gerrit todo # List all the CLs that await your review.
442 $ gerrit mine # List all of your open CLs.
443 $ gerrit inspect 28123 # Inspect CL 28123 on the public gerrit.
444 $ gerrit inspect *28123 # Inspect CL 28123 on the internal gerrit.
445 $ gerrit verify 28123 1 # Mark CL 28123 as verified (+1).
Mike Frysingerd8f841c2014-06-15 00:48:26 -0700446Scripting:
Mike Frysinger88f27292014-06-17 09:40:45 -0700447 $ gerrit ready `gerrit --raw mine` 1 # Mark *ALL* of your public CLs \
448ready.
449 $ gerrit ready `gerrit --raw -i mine` 1 # Mark *ALL* of your internal CLs \
450ready.
Mike Frysinger13f23a42013-05-13 17:32:01 -0400451
452Actions:"""
453 indent = max([len(x) - len(act_pfx) for x in actions])
454 for a in sorted(actions):
Mike Frysinger15b23e42014-12-05 17:00:05 -0500455 cmd = a[len(act_pfx):]
456 # Sanity check for devs adding new commands. Should be quick.
457 if cmd != cmd.lower().capitalize():
458 raise RuntimeError('callback "%s" is misnamed; should be "%s"' %
459 (cmd, cmd.lower().capitalize()))
460 usage += '\n %-*s: %s' % (indent, cmd.lower(), globals()[a].__doc__)
Mike Frysinger13f23a42013-05-13 17:32:01 -0400461
Mike Frysingerddf86eb2014-02-07 22:51:41 -0500462 parser = commandline.ArgumentParser(usage=usage)
Mike Frysinger08737512014-02-07 22:58:26 -0500463 parser.add_argument('-i', '--internal', dest='gob', action='store_const',
Mike Frysinger88f27292014-06-17 09:40:45 -0700464 default=constants.EXTERNAL_GOB_INSTANCE,
Mike Frysinger40541c62014-02-08 04:38:37 -0500465 const=constants.INTERNAL_GOB_INSTANCE,
Mike Frysinger08737512014-02-07 22:58:26 -0500466 help='Query internal Chromium Gerrit instance')
467 parser.add_argument('-g', '--gob',
Mike Frysinger88f27292014-06-17 09:40:45 -0700468 default=constants.EXTERNAL_GOB_INSTANCE,
Mike Frysingerd6e2df02014-11-26 02:55:04 -0500469 help=('Gerrit (on borg) instance to query (default: %s)' %
470 (constants.EXTERNAL_GOB_INSTANCE)))
Mike Frysingerddf86eb2014-02-07 22:51:41 -0500471 parser.add_argument('--sort', default='number',
472 help='Key to sort on (number, project)')
Mike Frysingerf70bdc72014-06-15 00:44:06 -0700473 parser.add_argument('--raw', default=False, action='store_true',
474 help='Return raw results (suitable for scripting)')
Mike Frysinger550d9aa2014-06-15 00:55:31 -0700475 parser.add_argument('-n', '--dry-run', default=False, action='store_true',
476 dest='dryrun',
477 help='Show what would be done, but do not make changes')
Mike Frysingerddf86eb2014-02-07 22:51:41 -0500478 parser.add_argument('-v', '--verbose', default=False, action='store_true',
479 help='Be more verbose in output')
Vadim Bendebury6e057b32014-12-29 09:41:36 -0800480 parser.add_argument('-b', '--branch',
481 help='Limit output to the specific branch')
Mathieu Olivariedc45b82015-01-12 19:43:20 -0800482 parser.add_argument('-p', '--project',
483 help='Limit output to the specific project')
Mathieu Olivari14645a12015-01-16 15:41:32 -0800484 parser.add_argument('-t', '--topic',
485 help='Limit output to the specific topic')
Mike Frysingerddf86eb2014-02-07 22:51:41 -0500486 parser.add_argument('args', nargs='+')
487 opts = parser.parse_args(argv)
Mike Frysinger13f23a42013-05-13 17:32:01 -0400488
Mike Frysinger88f27292014-06-17 09:40:45 -0700489 # A cache of gerrit helpers we'll load on demand.
490 opts.gerrit = {}
491 opts.Freeze()
492
Mike Frysinger031ad0b2013-05-14 18:15:34 -0400493 # pylint: disable=W0603
494 global COLOR
495 COLOR = terminal.Color(enabled=opts.color)
496
Mike Frysinger13f23a42013-05-13 17:32:01 -0400497 # Now look up the requested user action and run it.
Mike Frysinger88f27292014-06-17 09:40:45 -0700498 cmd = opts.args[0].lower()
499 args = opts.args[1:]
Mike Frysinger13f23a42013-05-13 17:32:01 -0400500 functor = globals().get(act_pfx + cmd.capitalize())
501 if functor:
502 argspec = inspect.getargspec(functor)
Vadim Bendeburydcfe2322013-05-23 10:54:49 -0700503 if argspec.varargs:
Mike Frysingerd8f841c2014-06-15 00:48:26 -0700504 arg_min = getattr(functor, 'arg_min', len(argspec.args))
505 if len(args) < arg_min:
Vadim Bendeburydcfe2322013-05-23 10:54:49 -0700506 parser.error('incorrect number of args: %s expects at least %s' %
Mike Frysingerd8f841c2014-06-15 00:48:26 -0700507 (cmd, arg_min))
Vadim Bendeburydcfe2322013-05-23 10:54:49 -0700508 elif len(argspec.args) - 1 != len(args):
Mike Frysinger13f23a42013-05-13 17:32:01 -0400509 parser.error('incorrect number of args: %s expects %s' %
510 (cmd, len(argspec.args) - 1))
Vadim Bendebury614f8682013-05-23 10:33:35 -0700511 try:
512 functor(opts, *args)
Mike Frysingerc85d8162014-02-08 00:45:21 -0500513 except (cros_build_lib.RunCommandError, gerrit.GerritException,
514 gob_util.GOBError) as e:
Vadim Bendeburydcfe2322013-05-23 10:54:49 -0700515 cros_build_lib.Die(e.message)
Mike Frysinger13f23a42013-05-13 17:32:01 -0400516 else:
517 parser.error('unknown action: %s' % (cmd,))