blob: b180aeddaac416dc6e6902b4680f286e92624879 [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
Aviv Keshetb7519e12016-10-04 00:50:00 -070018from chromite.lib import config_lib
19from chromite.lib import constants
Mike Frysinger13f23a42013-05-13 17:32:01 -040020from chromite.lib import commandline
21from chromite.lib import cros_build_lib
Ralph Nathan446aee92015-03-23 14:44:56 -070022from chromite.lib import cros_logging as logging
Mike Frysinger13f23a42013-05-13 17:32:01 -040023from chromite.lib import gerrit
Mathieu Olivari04b4d522014-12-18 17:26:34 -080024from chromite.lib import git
Mike Frysingerc85d8162014-02-08 00:45:21 -050025from chromite.lib import gob_util
Mike Frysinger13f23a42013-05-13 17:32:01 -040026from chromite.lib import terminal
27
28
Matthew Sartorid2e6bdf2015-07-23 12:07:39 -070029site_config = config_lib.GetConfig()
30
31
Mike Frysinger031ad0b2013-05-14 18:15:34 -040032COLOR = None
Mike Frysinger13f23a42013-05-13 17:32:01 -040033
34# Map the internal names to the ones we normally show on the web ui.
35GERRIT_APPROVAL_MAP = {
Vadim Bendebury50571832013-11-12 10:43:19 -080036 'COMR': ['CQ', 'Commit Queue ',],
37 'CRVW': ['CR', 'Code Review ',],
38 'SUBM': ['S ', 'Submitted ',],
David James2b2e2c52014-12-02 19:32:07 -080039 'TRY': ['T ', 'Trybot Ready ',],
Vadim Bendebury50571832013-11-12 10:43:19 -080040 'VRIF': ['V ', 'Verified ',],
Mike Frysinger13f23a42013-05-13 17:32:01 -040041}
42
43# Order is important -- matches the web ui. This also controls the short
44# entries that we summarize in non-verbose mode.
45GERRIT_SUMMARY_CATS = ('CR', 'CQ', 'V',)
46
47
48def red(s):
49 return COLOR.Color(terminal.Color.RED, s)
50
51
52def green(s):
53 return COLOR.Color(terminal.Color.GREEN, s)
54
55
56def blue(s):
57 return COLOR.Color(terminal.Color.BLUE, s)
58
59
60def limits(cls):
61 """Given a dict of fields, calculate the longest string lengths
62
63 This allows you to easily format the output of many results so that the
64 various cols all line up correctly.
65 """
66 lims = {}
67 for cl in cls:
68 for k in cl.keys():
Mike Frysingerf16b8f02013-10-21 22:24:46 -040069 # Use %s rather than str() to avoid codec issues.
70 # We also do this so we can format integers.
71 lims[k] = max(lims.get(k, 0), len('%s' % cl[k]))
Mike Frysinger13f23a42013-05-13 17:32:01 -040072 return lims
73
74
Mike Frysinger88f27292014-06-17 09:40:45 -070075# TODO: This func really needs to be merged into the core gerrit logic.
76def GetGerrit(opts, cl=None):
77 """Auto pick the right gerrit instance based on the |cl|
78
79 Args:
80 opts: The general options object.
81 cl: A CL taking one of the forms: 1234 *1234 chromium:1234
82
83 Returns:
84 A tuple of a gerrit object and a sanitized CL #.
85 """
86 gob = opts.gob
Paul Hobbs89765232015-06-24 14:07:49 -070087 if cl is not None:
Mike Frysinger88f27292014-06-17 09:40:45 -070088 if cl.startswith('*'):
Matthew Sartorid2e6bdf2015-07-23 12:07:39 -070089 gob = site_config.params.INTERNAL_GOB_INSTANCE
Mike Frysinger88f27292014-06-17 09:40:45 -070090 cl = cl[1:]
91 elif ':' in cl:
92 gob, cl = cl.split(':', 1)
93
94 if not gob in opts.gerrit:
95 opts.gerrit[gob] = gerrit.GetGerritHelper(gob=gob, print_cmd=opts.debug)
96
97 return (opts.gerrit[gob], cl)
98
99
Mike Frysinger13f23a42013-05-13 17:32:01 -0400100def GetApprovalSummary(_opts, cls):
101 """Return a dict of the most important approvals"""
102 approvs = dict([(x, '') for x in GERRIT_SUMMARY_CATS])
103 if 'approvals' in cls['currentPatchSet']:
104 for approver in cls['currentPatchSet']['approvals']:
105 cats = GERRIT_APPROVAL_MAP.get(approver['type'])
106 if not cats:
Ralph Nathan446aee92015-03-23 14:44:56 -0700107 logging.warning('unknown gerrit approval type: %s', approver['type'])
Mike Frysinger13f23a42013-05-13 17:32:01 -0400108 continue
109 cat = cats[0].strip()
110 val = int(approver['value'])
111 if not cat in approvs:
112 # Ignore the extended categories in the summary view.
113 continue
114 elif approvs[cat] is '':
115 approvs[cat] = val
116 elif val < 0:
117 approvs[cat] = min(approvs[cat], val)
118 else:
119 approvs[cat] = max(approvs[cat], val)
120 return approvs
121
122
123def PrintCl(opts, cls, lims, show_approvals=True):
124 """Pretty print a single result"""
Mike Frysingerf70bdc72014-06-15 00:44:06 -0700125 if opts.raw:
126 # Special case internal Chrome GoB as that is what most devs use.
127 # They can always redirect the list elsewhere via the -g option.
Matthew Sartorid2e6bdf2015-07-23 12:07:39 -0700128 if opts.gob == site_config.params.INTERNAL_GOB_INSTANCE:
Matthew Sartori19453d82015-07-27 15:16:31 -0700129 print(site_config.params.INTERNAL_CHANGE_PREFIX, end='')
Mike Frysingerf70bdc72014-06-15 00:44:06 -0700130 print(cls['number'])
131 return
132
Mike Frysinger13f23a42013-05-13 17:32:01 -0400133 if not lims:
134 lims = {'url': 0, 'project': 0}
135
136 status = ''
137 if show_approvals and not opts.verbose:
138 approvs = GetApprovalSummary(opts, cls)
139 for cat in GERRIT_SUMMARY_CATS:
140 if approvs[cat] is '':
141 functor = lambda x: x
142 elif approvs[cat] < 0:
143 functor = red
144 else:
145 functor = green
146 status += functor('%s:%2s ' % (cat, approvs[cat]))
147
Mike Frysinger31ff6f92014-02-08 04:33:03 -0500148 print('%s %s%-*s %s' % (blue('%-*s' % (lims['url'], cls['url'])), status,
149 lims['project'], cls['project'], cls['subject']))
Mike Frysinger13f23a42013-05-13 17:32:01 -0400150
151 if show_approvals and opts.verbose:
152 for approver in cls['currentPatchSet'].get('approvals', []):
153 functor = red if int(approver['value']) < 0 else green
154 n = functor('%2s' % approver['value'])
155 t = GERRIT_APPROVAL_MAP.get(approver['type'], [approver['type'],
156 approver['type']])[1]
Mike Frysinger31ff6f92014-02-08 04:33:03 -0500157 print(' %s %s %s' % (n, t, approver['by']['email']))
Mike Frysinger13f23a42013-05-13 17:32:01 -0400158
159
160def _MyUserInfo():
Mathieu Olivari04b4d522014-12-18 17:26:34 -0800161 email = git.GetProjectUserEmail(constants.CHROMITE_DIR)
162 [username, _, domain] = email.partition('@')
163 if domain in ('google.com', 'chromium.org'):
164 emails = ['%s@%s' % (username, domain)
165 for domain in ('google.com', 'chromium.org')]
166 else:
167 emails = [email]
Mike Frysinger13f23a42013-05-13 17:32:01 -0400168 reviewers = ['reviewer:%s' % x for x in emails]
169 owners = ['owner:%s' % x for x in emails]
170 return emails, reviewers, owners
171
172
Paul Hobbs89765232015-06-24 14:07:49 -0700173def _Query(opts, query, raw=True):
174 """Queries Gerrit with a query string built from the commandline options"""
Vadim Bendebury6e057b32014-12-29 09:41:36 -0800175 if opts.branch is not None:
176 query += ' branch:%s' % opts.branch
Mathieu Olivariedc45b82015-01-12 19:43:20 -0800177 if opts.project is not None:
178 query += ' project: %s' % opts.project
Mathieu Olivari14645a12015-01-16 15:41:32 -0800179 if opts.topic is not None:
180 query += ' topic: %s' % opts.topic
Vadim Bendebury6e057b32014-12-29 09:41:36 -0800181
Mike Frysinger88f27292014-06-17 09:40:45 -0700182 helper, _ = GetGerrit(opts)
Paul Hobbs89765232015-06-24 14:07:49 -0700183 return helper.Query(query, raw=raw, bypass_cache=False)
184
185
186def FilteredQuery(opts, query):
187 """Query gerrit and filter/clean up the results"""
188 ret = []
189
190 for cl in _Query(opts, query, raw=True):
Mike Frysinger13f23a42013-05-13 17:32:01 -0400191 # Gerrit likes to return a stats record too.
192 if not 'project' in cl:
193 continue
194
195 # Strip off common leading names since the result is still
196 # unique over the whole tree.
197 if not opts.verbose:
Mike Frysingere5e78272014-06-15 00:41:30 -0700198 for pfx in ('chromeos', 'chromiumos', 'overlays', 'platform',
199 'third_party'):
Mike Frysinger13f23a42013-05-13 17:32:01 -0400200 if cl['project'].startswith('%s/' % pfx):
201 cl['project'] = cl['project'][len(pfx) + 1:]
202
203 ret.append(cl)
204
Paul Hobbs89765232015-06-24 14:07:49 -0700205 if opts.sort == 'number':
Mike Frysinger13f23a42013-05-13 17:32:01 -0400206 key = lambda x: int(x[opts.sort])
207 else:
208 key = lambda x: x[opts.sort]
209 return sorted(ret, key=key)
210
211
Mike Frysinger13f23a42013-05-13 17:32:01 -0400212def IsApprover(cl, users):
213 """See if the approvers in |cl| is listed in |users|"""
214 # See if we are listed in the approvals list. We have to parse
215 # this by hand as the gerrit query system doesn't support it :(
216 # http://code.google.com/p/gerrit/issues/detail?id=1235
217 if 'approvals' not in cl['currentPatchSet']:
218 return False
219
220 if isinstance(users, basestring):
221 users = (users,)
222
223 for approver in cl['currentPatchSet']['approvals']:
Stefan Zager29560302013-09-06 14:30:54 -0700224 if (approver['by']['email'] in users and
225 approver['type'] == 'CRVW' and
226 int(approver['value']) != 0):
Mike Frysinger13f23a42013-05-13 17:32:01 -0400227 return True
228
229 return False
230
231
232def UserActTodo(opts):
233 """List CLs needing your review"""
234 emails, reviewers, owners = _MyUserInfo()
Mike Frysingerd6e2df02014-11-26 02:55:04 -0500235 cls = FilteredQuery(opts, ('( %s ) status:open NOT ( %s )' %
236 (' OR '.join(reviewers), ' OR '.join(owners))))
Mike Frysinger13f23a42013-05-13 17:32:01 -0400237 cls = [x for x in cls if not IsApprover(x, emails)]
238 lims = limits(cls)
239 for cl in cls:
240 PrintCl(opts, cl, lims)
241
242
Mike Frysingera1db2c42014-06-15 00:42:48 -0700243def UserActSearch(opts, query):
244 """List CLs matching the Gerrit <search query>"""
245 cls = FilteredQuery(opts, query)
Mike Frysinger13f23a42013-05-13 17:32:01 -0400246 lims = limits(cls)
247 for cl in cls:
248 PrintCl(opts, cl, lims)
249
250
Mike Frysingera1db2c42014-06-15 00:42:48 -0700251def UserActMine(opts):
252 """List your CLs with review statuses"""
253 _, _, owners = _MyUserInfo()
Vadim Bendebury0278a7e2015-09-05 15:23:13 -0700254 if opts.draft:
255 rule = 'is:draft'
256 else:
257 rule = 'status:new'
258 UserActSearch(opts, '( %s ) %s' % (' OR '.join(owners), rule))
Mike Frysingera1db2c42014-06-15 00:42:48 -0700259
260
Paul Hobbs89765232015-06-24 14:07:49 -0700261def _BreadthFirstSearch(to_visit, children, visited_key=lambda x: x):
262 """Runs breadth first search starting from the nodes in |to_visit|
263
264 Args:
265 to_visit: the starting nodes
266 children: a function which takes a node and returns the nodes adjacent to it
267 visited_key: a function for deduplicating node visits. Defaults to the
268 identity function (lambda x: x)
269
270 Returns:
271 A list of nodes which are reachable from any node in |to_visit| by calling
272 |children| any number of times.
273 """
274 to_visit = list(to_visit)
275 seen = set(map(visited_key, to_visit))
276 for node in to_visit:
277 for child in children(node):
278 key = visited_key(child)
279 if key not in seen:
280 seen.add(key)
281 to_visit.append(child)
282 return to_visit
283
284
285def UserActDeps(opts, query):
286 """List CLs matching a query, and all transitive dependencies of those CLs"""
287 cls = _Query(opts, query, raw=False)
288
289 @cros_build_lib.Memoize
290 def _QueryChange(cl):
291 return _Query(opts, cl, raw=False)
292
293 def _Children(cl):
294 """Returns the Gerrit and CQ-Depends dependencies of a patch"""
295 cq_deps = cl.PaladinDependencies(None)
296 direct_deps = cl.GerritDependencies() + cq_deps
297 # We need to query the change to guarantee that we have a .gerrit_number
298 for dep in direct_deps:
299 # TODO(phobbs) this should maybe catch network errors.
300 change = _QueryChange(dep.ToGerritQueryText())[-1]
301 if change.status == 'NEW':
302 yield change
303
304 transitives = _BreadthFirstSearch(
305 cls, _Children,
306 visited_key=lambda cl: cl.gerrit_number)
307
308 transitives_raw = [cl.patch_dict for cl in transitives]
309 lims = limits(transitives_raw)
310 for cl in transitives_raw:
311 PrintCl(opts, cl, lims)
312
313
Mike Frysingerd8f841c2014-06-15 00:48:26 -0700314def UserActInspect(opts, *args):
315 """Inspect CL number <n> [n ...]"""
Mike Frysinger88f27292014-06-17 09:40:45 -0700316 for arg in args:
317 cl = FilteredQuery(opts, arg)
Mike Frysingerd8f841c2014-06-15 00:48:26 -0700318 if cl:
319 PrintCl(opts, cl[0], None)
320 else:
Mike Frysinger88f27292014-06-17 09:40:45 -0700321 print('no results found for CL %s' % arg)
Mike Frysinger13f23a42013-05-13 17:32:01 -0400322
323
Mike Frysingerd8f841c2014-06-15 00:48:26 -0700324def UserActReview(opts, *args):
325 """Mark CL <n> [n ...] with code review status <-2,-1,0,1,2>"""
326 num = args[-1]
Mike Frysinger88f27292014-06-17 09:40:45 -0700327 for arg in args[:-1]:
328 helper, cl = GetGerrit(opts, arg)
329 helper.SetReview(cl, labels={'Code-Review': num}, dryrun=opts.dryrun)
Mike Frysingerd8f841c2014-06-15 00:48:26 -0700330UserActReview.arg_min = 2
Mike Frysinger13f23a42013-05-13 17:32:01 -0400331
332
Mike Frysingerd8f841c2014-06-15 00:48:26 -0700333def UserActVerify(opts, *args):
334 """Mark CL <n> [n ...] with verify status <-1,0,1>"""
335 num = args[-1]
Mike Frysinger88f27292014-06-17 09:40:45 -0700336 for arg in args[:-1]:
337 helper, cl = GetGerrit(opts, arg)
338 helper.SetReview(cl, labels={'Verified': num}, dryrun=opts.dryrun)
Mike Frysingerd8f841c2014-06-15 00:48:26 -0700339UserActVerify.arg_min = 2
Mike Frysinger13f23a42013-05-13 17:32:01 -0400340
341
Mike Frysingerd8f841c2014-06-15 00:48:26 -0700342def UserActReady(opts, *args):
343 """Mark CL <n> [n ...] with ready status <0,1,2>"""
344 num = args[-1]
Mike Frysinger88f27292014-06-17 09:40:45 -0700345 for arg in args[:-1]:
346 helper, cl = GetGerrit(opts, arg)
347 helper.SetReview(cl, labels={'Commit-Queue': num}, dryrun=opts.dryrun)
Mike Frysingerd8f841c2014-06-15 00:48:26 -0700348UserActReady.arg_min = 2
Mike Frysinger13f23a42013-05-13 17:32:01 -0400349
350
Mike Frysinger15b23e42014-12-05 17:00:05 -0500351def UserActTrybotready(opts, *args):
352 """Mark CL <n> [n ...] with trybot-ready status <0,1>"""
353 num = args[-1]
354 for arg in args[:-1]:
355 helper, cl = GetGerrit(opts, arg)
356 helper.SetReview(cl, labels={'Trybot-Ready': num}, dryrun=opts.dryrun)
357UserActTrybotready.arg_min = 2
358
359
Mike Frysingerd8f841c2014-06-15 00:48:26 -0700360def UserActSubmit(opts, *args):
361 """Submit CL <n> [n ...]"""
Mike Frysinger88f27292014-06-17 09:40:45 -0700362 for arg in args:
363 helper, cl = GetGerrit(opts, arg)
364 helper.SubmitChange(cl, dryrun=opts.dryrun)
Mike Frysinger13f23a42013-05-13 17:32:01 -0400365
366
Mike Frysingerd8f841c2014-06-15 00:48:26 -0700367def UserActAbandon(opts, *args):
368 """Abandon CL <n> [n ...]"""
Mike Frysinger88f27292014-06-17 09:40:45 -0700369 for arg in args:
370 helper, cl = GetGerrit(opts, arg)
371 helper.AbandonChange(cl, dryrun=opts.dryrun)
Mike Frysinger13f23a42013-05-13 17:32:01 -0400372
373
Mike Frysingerd8f841c2014-06-15 00:48:26 -0700374def UserActRestore(opts, *args):
375 """Restore CL <n> [n ...] that was abandoned"""
Mike Frysinger88f27292014-06-17 09:40:45 -0700376 for arg in args:
377 helper, cl = GetGerrit(opts, arg)
378 helper.RestoreChange(cl, dryrun=opts.dryrun)
Mike Frysinger13f23a42013-05-13 17:32:01 -0400379
380
Mike Frysinger88f27292014-06-17 09:40:45 -0700381def UserActReviewers(opts, cl, *args):
Vadim Bendeburydcfe2322013-05-23 10:54:49 -0700382 """Add/remove reviewers' emails for CL <n> (prepend with '~' to remove)"""
Mike Frysingerc15efa52013-12-12 01:13:56 -0500383 emails = args
Vadim Bendeburydcfe2322013-05-23 10:54:49 -0700384 # Allow for optional leading '~'.
385 email_validator = re.compile(r'^[~]?%s$' % constants.EMAIL_REGEX)
386 add_list, remove_list, invalid_list = [], [], []
387
388 for x in emails:
389 if not email_validator.match(x):
390 invalid_list.append(x)
391 elif x[0] == '~':
392 remove_list.append(x[1:])
393 else:
394 add_list.append(x)
395
396 if invalid_list:
397 cros_build_lib.Die(
398 'Invalid email address(es): %s' % ', '.join(invalid_list))
399
400 if add_list or remove_list:
Mike Frysinger88f27292014-06-17 09:40:45 -0700401 helper, cl = GetGerrit(opts, cl)
402 helper.SetReviewers(cl, add=add_list, remove=remove_list,
403 dryrun=opts.dryrun)
Vadim Bendeburydcfe2322013-05-23 10:54:49 -0700404
405
Mike Frysinger88f27292014-06-17 09:40:45 -0700406def UserActMessage(opts, cl, message):
Doug Anderson8119df02013-07-20 21:00:24 +0530407 """Add a message to CL <n>"""
Mike Frysinger88f27292014-06-17 09:40:45 -0700408 helper, cl = GetGerrit(opts, cl)
409 helper.SetReview(cl, msg=message, dryrun=opts.dryrun)
Doug Anderson8119df02013-07-20 21:00:24 +0530410
411
Mathieu Olivari02f89b32015-01-09 13:53:38 -0800412def UserActTopic(opts, topic, *args):
413 """Set |topic| for CL number <n> [n ...]"""
414 for arg in args:
415 helper, arg = GetGerrit(opts, arg)
416 helper.SetTopic(arg, topic, dryrun=opts.dryrun)
417
418
Mike Frysingerd8f841c2014-06-15 00:48:26 -0700419def UserActDeletedraft(opts, *args):
Marc Herbert02448c82015-10-07 14:03:34 -0700420 """Delete draft CL <n> [n ...]"""
Mike Frysinger88f27292014-06-17 09:40:45 -0700421 for arg in args:
422 helper, cl = GetGerrit(opts, arg)
423 helper.DeleteDraft(cl, dryrun=opts.dryrun)
Jon Salza427fb02014-03-07 18:13:17 +0800424
425
Yu-Ju Hongc20d7b32014-11-18 07:51:11 -0800426def UserActAccount(opts):
427 """Get user account information."""
428 helper, _ = GetGerrit(opts)
429 pprint.PrettyPrinter().pprint(helper.GetAccount())
430
431
Mike Frysinger13f23a42013-05-13 17:32:01 -0400432def main(argv):
433 # Locate actions that are exposed to the user. All functions that start
434 # with "UserAct" are fair game.
435 act_pfx = 'UserAct'
436 actions = [x for x in globals() if x.startswith(act_pfx)]
437
Mike Frysingerddf86eb2014-02-07 22:51:41 -0500438 usage = """%(prog)s [options] <action> [action args]
Mike Frysinger13f23a42013-05-13 17:32:01 -0400439
440There is no support for doing line-by-line code review via the command line.
441This helps you manage various bits and CL status.
442
Mike Frysingera1db2c42014-06-15 00:42:48 -0700443For general Gerrit documentation, see:
444 https://gerrit-review.googlesource.com/Documentation/
445The Searching Changes page covers the search query syntax:
446 https://gerrit-review.googlesource.com/Documentation/user-search.html
447
Mike Frysinger13f23a42013-05-13 17:32:01 -0400448Example:
449 $ gerrit todo # List all the CLs that await your review.
450 $ gerrit mine # List all of your open CLs.
451 $ gerrit inspect 28123 # Inspect CL 28123 on the public gerrit.
452 $ gerrit inspect *28123 # Inspect CL 28123 on the internal gerrit.
453 $ gerrit verify 28123 1 # Mark CL 28123 as verified (+1).
Mike Frysingerd8f841c2014-06-15 00:48:26 -0700454Scripting:
Mike Frysinger88f27292014-06-17 09:40:45 -0700455 $ gerrit ready `gerrit --raw mine` 1 # Mark *ALL* of your public CLs \
456ready.
457 $ gerrit ready `gerrit --raw -i mine` 1 # Mark *ALL* of your internal CLs \
458ready.
Mike Frysinger13f23a42013-05-13 17:32:01 -0400459
460Actions:"""
461 indent = max([len(x) - len(act_pfx) for x in actions])
462 for a in sorted(actions):
Mike Frysinger15b23e42014-12-05 17:00:05 -0500463 cmd = a[len(act_pfx):]
464 # Sanity check for devs adding new commands. Should be quick.
465 if cmd != cmd.lower().capitalize():
466 raise RuntimeError('callback "%s" is misnamed; should be "%s"' %
467 (cmd, cmd.lower().capitalize()))
468 usage += '\n %-*s: %s' % (indent, cmd.lower(), globals()[a].__doc__)
Mike Frysinger13f23a42013-05-13 17:32:01 -0400469
Mike Frysingerddf86eb2014-02-07 22:51:41 -0500470 parser = commandline.ArgumentParser(usage=usage)
Mike Frysinger08737512014-02-07 22:58:26 -0500471 parser.add_argument('-i', '--internal', dest='gob', action='store_const',
Matthew Sartorid2e6bdf2015-07-23 12:07:39 -0700472 default=site_config.params.EXTERNAL_GOB_INSTANCE,
473 const=site_config.params.INTERNAL_GOB_INSTANCE,
Mike Frysinger08737512014-02-07 22:58:26 -0500474 help='Query internal Chromium Gerrit instance')
475 parser.add_argument('-g', '--gob',
Matthew Sartorid2e6bdf2015-07-23 12:07:39 -0700476 default=site_config.params.EXTERNAL_GOB_INSTANCE,
Mike Frysingerd6e2df02014-11-26 02:55:04 -0500477 help=('Gerrit (on borg) instance to query (default: %s)' %
Matthew Sartorid2e6bdf2015-07-23 12:07:39 -0700478 (site_config.params.EXTERNAL_GOB_INSTANCE)))
Mike Frysingerddf86eb2014-02-07 22:51:41 -0500479 parser.add_argument('--sort', default='number',
480 help='Key to sort on (number, project)')
Mike Frysingerf70bdc72014-06-15 00:44:06 -0700481 parser.add_argument('--raw', default=False, action='store_true',
482 help='Return raw results (suitable for scripting)')
Mike Frysinger550d9aa2014-06-15 00:55:31 -0700483 parser.add_argument('-n', '--dry-run', default=False, action='store_true',
484 dest='dryrun',
485 help='Show what would be done, but do not make changes')
Mike Frysingerddf86eb2014-02-07 22:51:41 -0500486 parser.add_argument('-v', '--verbose', default=False, action='store_true',
487 help='Be more verbose in output')
Vadim Bendebury6e057b32014-12-29 09:41:36 -0800488 parser.add_argument('-b', '--branch',
489 help='Limit output to the specific branch')
Vadim Bendebury0278a7e2015-09-05 15:23:13 -0700490 parser.add_argument('--draft', default=False, action='store_true',
491 help="Show draft changes (applicable to 'mine' only)")
Mathieu Olivariedc45b82015-01-12 19:43:20 -0800492 parser.add_argument('-p', '--project',
493 help='Limit output to the specific project')
Mathieu Olivari14645a12015-01-16 15:41:32 -0800494 parser.add_argument('-t', '--topic',
495 help='Limit output to the specific topic')
Mike Frysingerddf86eb2014-02-07 22:51:41 -0500496 parser.add_argument('args', nargs='+')
497 opts = parser.parse_args(argv)
Mike Frysinger13f23a42013-05-13 17:32:01 -0400498
Mike Frysinger88f27292014-06-17 09:40:45 -0700499 # A cache of gerrit helpers we'll load on demand.
500 opts.gerrit = {}
501 opts.Freeze()
502
Mike Frysinger031ad0b2013-05-14 18:15:34 -0400503 # pylint: disable=W0603
504 global COLOR
505 COLOR = terminal.Color(enabled=opts.color)
506
Mike Frysinger13f23a42013-05-13 17:32:01 -0400507 # Now look up the requested user action and run it.
Mike Frysinger88f27292014-06-17 09:40:45 -0700508 cmd = opts.args[0].lower()
509 args = opts.args[1:]
Mike Frysinger13f23a42013-05-13 17:32:01 -0400510 functor = globals().get(act_pfx + cmd.capitalize())
511 if functor:
512 argspec = inspect.getargspec(functor)
Vadim Bendeburydcfe2322013-05-23 10:54:49 -0700513 if argspec.varargs:
Mike Frysingerd8f841c2014-06-15 00:48:26 -0700514 arg_min = getattr(functor, 'arg_min', len(argspec.args))
515 if len(args) < arg_min:
Vadim Bendeburydcfe2322013-05-23 10:54:49 -0700516 parser.error('incorrect number of args: %s expects at least %s' %
Mike Frysingerd8f841c2014-06-15 00:48:26 -0700517 (cmd, arg_min))
Vadim Bendeburydcfe2322013-05-23 10:54:49 -0700518 elif len(argspec.args) - 1 != len(args):
Mike Frysinger13f23a42013-05-13 17:32:01 -0400519 parser.error('incorrect number of args: %s expects %s' %
520 (cmd, len(argspec.args) - 1))
Vadim Bendebury614f8682013-05-23 10:33:35 -0700521 try:
522 functor(opts, *args)
Mike Frysingerc85d8162014-02-08 00:45:21 -0500523 except (cros_build_lib.RunCommandError, gerrit.GerritException,
524 gob_util.GOBError) as e:
Vadim Bendeburydcfe2322013-05-23 10:54:49 -0700525 cros_build_lib.Die(e.message)
Mike Frysinger13f23a42013-05-13 17:32:01 -0400526 else:
527 parser.error('unknown action: %s' % (cmd,))