blob: e7f457838224c81118a355920f742cf9f30ee738 [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():
Mike Frysinger2cd56022017-01-12 20:56:27 -0500161 """Try to return e-mail addresses used by the active user."""
162 return [git.GetProjectUserEmail(constants.CHROMITE_DIR)]
Mike Frysinger13f23a42013-05-13 17:32:01 -0400163
164
Paul Hobbs89765232015-06-24 14:07:49 -0700165def _Query(opts, query, raw=True):
166 """Queries Gerrit with a query string built from the commandline options"""
Vadim Bendebury6e057b32014-12-29 09:41:36 -0800167 if opts.branch is not None:
168 query += ' branch:%s' % opts.branch
Mathieu Olivariedc45b82015-01-12 19:43:20 -0800169 if opts.project is not None:
170 query += ' project: %s' % opts.project
Mathieu Olivari14645a12015-01-16 15:41:32 -0800171 if opts.topic is not None:
172 query += ' topic: %s' % opts.topic
Vadim Bendebury6e057b32014-12-29 09:41:36 -0800173
Mike Frysinger88f27292014-06-17 09:40:45 -0700174 helper, _ = GetGerrit(opts)
Paul Hobbs89765232015-06-24 14:07:49 -0700175 return helper.Query(query, raw=raw, bypass_cache=False)
176
177
178def FilteredQuery(opts, query):
179 """Query gerrit and filter/clean up the results"""
180 ret = []
181
Mike Frysinger2cd56022017-01-12 20:56:27 -0500182 logging.debug('Running query: %s', query)
Paul Hobbs89765232015-06-24 14:07:49 -0700183 for cl in _Query(opts, query, raw=True):
Mike Frysinger13f23a42013-05-13 17:32:01 -0400184 # Gerrit likes to return a stats record too.
185 if not 'project' in cl:
186 continue
187
188 # Strip off common leading names since the result is still
189 # unique over the whole tree.
190 if not opts.verbose:
Mike Frysingere5e78272014-06-15 00:41:30 -0700191 for pfx in ('chromeos', 'chromiumos', 'overlays', 'platform',
192 'third_party'):
Mike Frysinger13f23a42013-05-13 17:32:01 -0400193 if cl['project'].startswith('%s/' % pfx):
194 cl['project'] = cl['project'][len(pfx) + 1:]
195
196 ret.append(cl)
197
Paul Hobbs89765232015-06-24 14:07:49 -0700198 if opts.sort == 'number':
Mike Frysinger13f23a42013-05-13 17:32:01 -0400199 key = lambda x: int(x[opts.sort])
200 else:
201 key = lambda x: x[opts.sort]
202 return sorted(ret, key=key)
203
204
Mike Frysinger13f23a42013-05-13 17:32:01 -0400205def IsApprover(cl, users):
206 """See if the approvers in |cl| is listed in |users|"""
207 # See if we are listed in the approvals list. We have to parse
208 # this by hand as the gerrit query system doesn't support it :(
209 # http://code.google.com/p/gerrit/issues/detail?id=1235
210 if 'approvals' not in cl['currentPatchSet']:
211 return False
212
213 if isinstance(users, basestring):
214 users = (users,)
215
216 for approver in cl['currentPatchSet']['approvals']:
Stefan Zager29560302013-09-06 14:30:54 -0700217 if (approver['by']['email'] in users and
218 approver['type'] == 'CRVW' and
219 int(approver['value']) != 0):
Mike Frysinger13f23a42013-05-13 17:32:01 -0400220 return True
221
222 return False
223
224
225def UserActTodo(opts):
226 """List CLs needing your review"""
Mike Frysinger2cd56022017-01-12 20:56:27 -0500227 emails = _MyUserInfo()
228 cls = FilteredQuery(opts, 'reviewer:self status:open NOT owner:self')
Mike Frysinger13f23a42013-05-13 17:32:01 -0400229 cls = [x for x in cls if not IsApprover(x, emails)]
230 lims = limits(cls)
231 for cl in cls:
232 PrintCl(opts, cl, lims)
233
234
Mike Frysingera1db2c42014-06-15 00:42:48 -0700235def UserActSearch(opts, query):
236 """List CLs matching the Gerrit <search query>"""
237 cls = FilteredQuery(opts, query)
Mike Frysinger13f23a42013-05-13 17:32:01 -0400238 lims = limits(cls)
239 for cl in cls:
240 PrintCl(opts, cl, lims)
241
242
Mike Frysingera1db2c42014-06-15 00:42:48 -0700243def UserActMine(opts):
244 """List your CLs with review statuses"""
Vadim Bendebury0278a7e2015-09-05 15:23:13 -0700245 if opts.draft:
246 rule = 'is:draft'
247 else:
248 rule = 'status:new'
Mike Frysinger2cd56022017-01-12 20:56:27 -0500249 UserActSearch(opts, 'owner:self %s' % (rule,))
Mike Frysingera1db2c42014-06-15 00:42:48 -0700250
251
Paul Hobbs89765232015-06-24 14:07:49 -0700252def _BreadthFirstSearch(to_visit, children, visited_key=lambda x: x):
253 """Runs breadth first search starting from the nodes in |to_visit|
254
255 Args:
256 to_visit: the starting nodes
257 children: a function which takes a node and returns the nodes adjacent to it
258 visited_key: a function for deduplicating node visits. Defaults to the
259 identity function (lambda x: x)
260
261 Returns:
262 A list of nodes which are reachable from any node in |to_visit| by calling
263 |children| any number of times.
264 """
265 to_visit = list(to_visit)
266 seen = set(map(visited_key, to_visit))
267 for node in to_visit:
268 for child in children(node):
269 key = visited_key(child)
270 if key not in seen:
271 seen.add(key)
272 to_visit.append(child)
273 return to_visit
274
275
276def UserActDeps(opts, query):
277 """List CLs matching a query, and all transitive dependencies of those CLs"""
278 cls = _Query(opts, query, raw=False)
279
280 @cros_build_lib.Memoize
281 def _QueryChange(cl):
282 return _Query(opts, cl, raw=False)
283
284 def _Children(cl):
285 """Returns the Gerrit and CQ-Depends dependencies of a patch"""
286 cq_deps = cl.PaladinDependencies(None)
287 direct_deps = cl.GerritDependencies() + cq_deps
288 # We need to query the change to guarantee that we have a .gerrit_number
289 for dep in direct_deps:
290 # TODO(phobbs) this should maybe catch network errors.
291 change = _QueryChange(dep.ToGerritQueryText())[-1]
292 if change.status == 'NEW':
293 yield change
294
295 transitives = _BreadthFirstSearch(
296 cls, _Children,
297 visited_key=lambda cl: cl.gerrit_number)
298
299 transitives_raw = [cl.patch_dict for cl in transitives]
300 lims = limits(transitives_raw)
301 for cl in transitives_raw:
302 PrintCl(opts, cl, lims)
303
304
Mike Frysingerd8f841c2014-06-15 00:48:26 -0700305def UserActInspect(opts, *args):
306 """Inspect CL number <n> [n ...]"""
Mike Frysinger88f27292014-06-17 09:40:45 -0700307 for arg in args:
308 cl = FilteredQuery(opts, arg)
Mike Frysingerd8f841c2014-06-15 00:48:26 -0700309 if cl:
310 PrintCl(opts, cl[0], None)
311 else:
Mike Frysinger88f27292014-06-17 09:40:45 -0700312 print('no results found for CL %s' % arg)
Mike Frysinger13f23a42013-05-13 17:32:01 -0400313
314
Mike Frysingerd8f841c2014-06-15 00:48:26 -0700315def UserActReview(opts, *args):
316 """Mark CL <n> [n ...] with code review status <-2,-1,0,1,2>"""
317 num = args[-1]
Mike Frysinger88f27292014-06-17 09:40:45 -0700318 for arg in args[:-1]:
319 helper, cl = GetGerrit(opts, arg)
320 helper.SetReview(cl, labels={'Code-Review': num}, dryrun=opts.dryrun)
Mike Frysingerd8f841c2014-06-15 00:48:26 -0700321UserActReview.arg_min = 2
Mike Frysinger13f23a42013-05-13 17:32:01 -0400322
323
Mike Frysingerd8f841c2014-06-15 00:48:26 -0700324def UserActVerify(opts, *args):
325 """Mark CL <n> [n ...] with verify status <-1,0,1>"""
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={'Verified': num}, dryrun=opts.dryrun)
Mike Frysingerd8f841c2014-06-15 00:48:26 -0700330UserActVerify.arg_min = 2
Mike Frysinger13f23a42013-05-13 17:32:01 -0400331
332
Mike Frysingerd8f841c2014-06-15 00:48:26 -0700333def UserActReady(opts, *args):
334 """Mark CL <n> [n ...] with ready status <0,1,2>"""
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={'Commit-Queue': num}, dryrun=opts.dryrun)
Mike Frysingerd8f841c2014-06-15 00:48:26 -0700339UserActReady.arg_min = 2
Mike Frysinger13f23a42013-05-13 17:32:01 -0400340
341
Mike Frysinger15b23e42014-12-05 17:00:05 -0500342def UserActTrybotready(opts, *args):
343 """Mark CL <n> [n ...] with trybot-ready status <0,1>"""
344 num = args[-1]
345 for arg in args[:-1]:
346 helper, cl = GetGerrit(opts, arg)
347 helper.SetReview(cl, labels={'Trybot-Ready': num}, dryrun=opts.dryrun)
348UserActTrybotready.arg_min = 2
349
350
Mike Frysingerd8f841c2014-06-15 00:48:26 -0700351def UserActSubmit(opts, *args):
352 """Submit CL <n> [n ...]"""
Mike Frysinger88f27292014-06-17 09:40:45 -0700353 for arg in args:
354 helper, cl = GetGerrit(opts, arg)
355 helper.SubmitChange(cl, dryrun=opts.dryrun)
Mike Frysinger13f23a42013-05-13 17:32:01 -0400356
357
Mike Frysingerd8f841c2014-06-15 00:48:26 -0700358def UserActAbandon(opts, *args):
359 """Abandon CL <n> [n ...]"""
Mike Frysinger88f27292014-06-17 09:40:45 -0700360 for arg in args:
361 helper, cl = GetGerrit(opts, arg)
362 helper.AbandonChange(cl, dryrun=opts.dryrun)
Mike Frysinger13f23a42013-05-13 17:32:01 -0400363
364
Mike Frysingerd8f841c2014-06-15 00:48:26 -0700365def UserActRestore(opts, *args):
366 """Restore CL <n> [n ...] that was abandoned"""
Mike Frysinger88f27292014-06-17 09:40:45 -0700367 for arg in args:
368 helper, cl = GetGerrit(opts, arg)
369 helper.RestoreChange(cl, dryrun=opts.dryrun)
Mike Frysinger13f23a42013-05-13 17:32:01 -0400370
371
Mike Frysinger88f27292014-06-17 09:40:45 -0700372def UserActReviewers(opts, cl, *args):
Vadim Bendeburydcfe2322013-05-23 10:54:49 -0700373 """Add/remove reviewers' emails for CL <n> (prepend with '~' to remove)"""
Mike Frysingerc15efa52013-12-12 01:13:56 -0500374 emails = args
Vadim Bendeburydcfe2322013-05-23 10:54:49 -0700375 # Allow for optional leading '~'.
376 email_validator = re.compile(r'^[~]?%s$' % constants.EMAIL_REGEX)
377 add_list, remove_list, invalid_list = [], [], []
378
379 for x in emails:
380 if not email_validator.match(x):
381 invalid_list.append(x)
382 elif x[0] == '~':
383 remove_list.append(x[1:])
384 else:
385 add_list.append(x)
386
387 if invalid_list:
388 cros_build_lib.Die(
389 'Invalid email address(es): %s' % ', '.join(invalid_list))
390
391 if add_list or remove_list:
Mike Frysinger88f27292014-06-17 09:40:45 -0700392 helper, cl = GetGerrit(opts, cl)
393 helper.SetReviewers(cl, add=add_list, remove=remove_list,
394 dryrun=opts.dryrun)
Vadim Bendeburydcfe2322013-05-23 10:54:49 -0700395
396
Mike Frysinger88f27292014-06-17 09:40:45 -0700397def UserActMessage(opts, cl, message):
Doug Anderson8119df02013-07-20 21:00:24 +0530398 """Add a message to CL <n>"""
Mike Frysinger88f27292014-06-17 09:40:45 -0700399 helper, cl = GetGerrit(opts, cl)
400 helper.SetReview(cl, msg=message, dryrun=opts.dryrun)
Doug Anderson8119df02013-07-20 21:00:24 +0530401
402
Mathieu Olivari02f89b32015-01-09 13:53:38 -0800403def UserActTopic(opts, topic, *args):
404 """Set |topic| for CL number <n> [n ...]"""
405 for arg in args:
406 helper, arg = GetGerrit(opts, arg)
407 helper.SetTopic(arg, topic, dryrun=opts.dryrun)
408
409
Wei-Han Chenb4c9af52017-02-09 14:43:22 +0800410def UserActSethashtags(opts, cl, *args):
411 """Add/remove hashtags for CL <n> (prepend with '~' to remove)"""
412 hashtags = args
413 add = []
414 remove = []
415 for hashtag in hashtags:
416 if hashtag.startswith('~'):
417 remove.append(hashtag[1:])
418 else:
419 add.append(hashtag)
420 helper, cl = GetGerrit(opts, cl)
421 helper.SetHashtags(cl, add, remove, dryrun=opts.dryrun)
422
423
Mike Frysingerd8f841c2014-06-15 00:48:26 -0700424def UserActDeletedraft(opts, *args):
Marc Herbert02448c82015-10-07 14:03:34 -0700425 """Delete draft CL <n> [n ...]"""
Mike Frysinger88f27292014-06-17 09:40:45 -0700426 for arg in args:
427 helper, cl = GetGerrit(opts, arg)
428 helper.DeleteDraft(cl, dryrun=opts.dryrun)
Jon Salza427fb02014-03-07 18:13:17 +0800429
430
Yu-Ju Hongc20d7b32014-11-18 07:51:11 -0800431def UserActAccount(opts):
432 """Get user account information."""
433 helper, _ = GetGerrit(opts)
434 pprint.PrettyPrinter().pprint(helper.GetAccount())
435
436
Mike Frysinger13f23a42013-05-13 17:32:01 -0400437def main(argv):
438 # Locate actions that are exposed to the user. All functions that start
439 # with "UserAct" are fair game.
440 act_pfx = 'UserAct'
441 actions = [x for x in globals() if x.startswith(act_pfx)]
442
Mike Frysingerddf86eb2014-02-07 22:51:41 -0500443 usage = """%(prog)s [options] <action> [action args]
Mike Frysinger13f23a42013-05-13 17:32:01 -0400444
445There is no support for doing line-by-line code review via the command line.
446This helps you manage various bits and CL status.
447
Mike Frysingera1db2c42014-06-15 00:42:48 -0700448For general Gerrit documentation, see:
449 https://gerrit-review.googlesource.com/Documentation/
450The Searching Changes page covers the search query syntax:
451 https://gerrit-review.googlesource.com/Documentation/user-search.html
452
Mike Frysinger13f23a42013-05-13 17:32:01 -0400453Example:
454 $ gerrit todo # List all the CLs that await your review.
455 $ gerrit mine # List all of your open CLs.
456 $ gerrit inspect 28123 # Inspect CL 28123 on the public gerrit.
457 $ gerrit inspect *28123 # Inspect CL 28123 on the internal gerrit.
458 $ gerrit verify 28123 1 # Mark CL 28123 as verified (+1).
Mike Frysingerd8f841c2014-06-15 00:48:26 -0700459Scripting:
Mike Frysinger88f27292014-06-17 09:40:45 -0700460 $ gerrit ready `gerrit --raw mine` 1 # Mark *ALL* of your public CLs \
461ready.
462 $ gerrit ready `gerrit --raw -i mine` 1 # Mark *ALL* of your internal CLs \
463ready.
Mike Frysinger13f23a42013-05-13 17:32:01 -0400464
465Actions:"""
466 indent = max([len(x) - len(act_pfx) for x in actions])
467 for a in sorted(actions):
Mike Frysinger15b23e42014-12-05 17:00:05 -0500468 cmd = a[len(act_pfx):]
469 # Sanity check for devs adding new commands. Should be quick.
470 if cmd != cmd.lower().capitalize():
471 raise RuntimeError('callback "%s" is misnamed; should be "%s"' %
472 (cmd, cmd.lower().capitalize()))
473 usage += '\n %-*s: %s' % (indent, cmd.lower(), globals()[a].__doc__)
Mike Frysinger13f23a42013-05-13 17:32:01 -0400474
Mike Frysingerddf86eb2014-02-07 22:51:41 -0500475 parser = commandline.ArgumentParser(usage=usage)
Mike Frysinger08737512014-02-07 22:58:26 -0500476 parser.add_argument('-i', '--internal', dest='gob', action='store_const',
Matthew Sartorid2e6bdf2015-07-23 12:07:39 -0700477 default=site_config.params.EXTERNAL_GOB_INSTANCE,
478 const=site_config.params.INTERNAL_GOB_INSTANCE,
Mike Frysinger08737512014-02-07 22:58:26 -0500479 help='Query internal Chromium Gerrit instance')
480 parser.add_argument('-g', '--gob',
Matthew Sartorid2e6bdf2015-07-23 12:07:39 -0700481 default=site_config.params.EXTERNAL_GOB_INSTANCE,
Mike Frysingerd6e2df02014-11-26 02:55:04 -0500482 help=('Gerrit (on borg) instance to query (default: %s)' %
Matthew Sartorid2e6bdf2015-07-23 12:07:39 -0700483 (site_config.params.EXTERNAL_GOB_INSTANCE)))
Mike Frysingerddf86eb2014-02-07 22:51:41 -0500484 parser.add_argument('--sort', default='number',
485 help='Key to sort on (number, project)')
Mike Frysingerf70bdc72014-06-15 00:44:06 -0700486 parser.add_argument('--raw', default=False, action='store_true',
487 help='Return raw results (suitable for scripting)')
Mike Frysinger550d9aa2014-06-15 00:55:31 -0700488 parser.add_argument('-n', '--dry-run', default=False, action='store_true',
489 dest='dryrun',
490 help='Show what would be done, but do not make changes')
Mike Frysingerddf86eb2014-02-07 22:51:41 -0500491 parser.add_argument('-v', '--verbose', default=False, action='store_true',
492 help='Be more verbose in output')
Vadim Bendebury6e057b32014-12-29 09:41:36 -0800493 parser.add_argument('-b', '--branch',
494 help='Limit output to the specific branch')
Vadim Bendebury0278a7e2015-09-05 15:23:13 -0700495 parser.add_argument('--draft', default=False, action='store_true',
496 help="Show draft changes (applicable to 'mine' only)")
Mathieu Olivariedc45b82015-01-12 19:43:20 -0800497 parser.add_argument('-p', '--project',
498 help='Limit output to the specific project')
Mathieu Olivari14645a12015-01-16 15:41:32 -0800499 parser.add_argument('-t', '--topic',
500 help='Limit output to the specific topic')
Mike Frysingerddf86eb2014-02-07 22:51:41 -0500501 parser.add_argument('args', nargs='+')
502 opts = parser.parse_args(argv)
Mike Frysinger13f23a42013-05-13 17:32:01 -0400503
Mike Frysinger88f27292014-06-17 09:40:45 -0700504 # A cache of gerrit helpers we'll load on demand.
505 opts.gerrit = {}
506 opts.Freeze()
507
Mike Frysinger031ad0b2013-05-14 18:15:34 -0400508 # pylint: disable=W0603
509 global COLOR
510 COLOR = terminal.Color(enabled=opts.color)
511
Mike Frysinger13f23a42013-05-13 17:32:01 -0400512 # Now look up the requested user action and run it.
Mike Frysinger88f27292014-06-17 09:40:45 -0700513 cmd = opts.args[0].lower()
514 args = opts.args[1:]
Mike Frysinger13f23a42013-05-13 17:32:01 -0400515 functor = globals().get(act_pfx + cmd.capitalize())
516 if functor:
517 argspec = inspect.getargspec(functor)
Vadim Bendeburydcfe2322013-05-23 10:54:49 -0700518 if argspec.varargs:
Mike Frysingerd8f841c2014-06-15 00:48:26 -0700519 arg_min = getattr(functor, 'arg_min', len(argspec.args))
520 if len(args) < arg_min:
Vadim Bendeburydcfe2322013-05-23 10:54:49 -0700521 parser.error('incorrect number of args: %s expects at least %s' %
Mike Frysingerd8f841c2014-06-15 00:48:26 -0700522 (cmd, arg_min))
Vadim Bendeburydcfe2322013-05-23 10:54:49 -0700523 elif len(argspec.args) - 1 != len(args):
Mike Frysinger13f23a42013-05-13 17:32:01 -0400524 parser.error('incorrect number of args: %s expects %s' %
525 (cmd, len(argspec.args) - 1))
Vadim Bendebury614f8682013-05-23 10:33:35 -0700526 try:
527 functor(opts, *args)
Mike Frysingerc85d8162014-02-08 00:45:21 -0500528 except (cros_build_lib.RunCommandError, gerrit.GerritException,
529 gob_util.GOBError) as e:
Vadim Bendeburydcfe2322013-05-23 10:54:49 -0700530 cros_build_lib.Die(e.message)
Mike Frysinger13f23a42013-05-13 17:32:01 -0400531 else:
532 parser.error('unknown action: %s' % (cmd,))