blob: d5f1f0d3fa76761e3d8f8b6b2e0da65d027151c9 [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
Matthew Sartorid2e6bdf2015-07-23 12:07:39 -070018from chromite.cbuildbot import config_lib
Don Garrett88b8d782014-05-13 17:30:55 -070019from chromite.cbuildbot 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:
Mike Frysingerf70bdc72014-06-15 00:44:06 -0700129 print(constants.INTERNAL_CHANGE_PREFIX, end='')
130 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()
254 UserActSearch(opts, '( %s ) status:new' % (' OR '.join(owners),))
255
256
Paul Hobbs89765232015-06-24 14:07:49 -0700257def _BreadthFirstSearch(to_visit, children, visited_key=lambda x: x):
258 """Runs breadth first search starting from the nodes in |to_visit|
259
260 Args:
261 to_visit: the starting nodes
262 children: a function which takes a node and returns the nodes adjacent to it
263 visited_key: a function for deduplicating node visits. Defaults to the
264 identity function (lambda x: x)
265
266 Returns:
267 A list of nodes which are reachable from any node in |to_visit| by calling
268 |children| any number of times.
269 """
270 to_visit = list(to_visit)
271 seen = set(map(visited_key, to_visit))
272 for node in to_visit:
273 for child in children(node):
274 key = visited_key(child)
275 if key not in seen:
276 seen.add(key)
277 to_visit.append(child)
278 return to_visit
279
280
281def UserActDeps(opts, query):
282 """List CLs matching a query, and all transitive dependencies of those CLs"""
283 cls = _Query(opts, query, raw=False)
284
285 @cros_build_lib.Memoize
286 def _QueryChange(cl):
287 return _Query(opts, cl, raw=False)
288
289 def _Children(cl):
290 """Returns the Gerrit and CQ-Depends dependencies of a patch"""
291 cq_deps = cl.PaladinDependencies(None)
292 direct_deps = cl.GerritDependencies() + cq_deps
293 # We need to query the change to guarantee that we have a .gerrit_number
294 for dep in direct_deps:
295 # TODO(phobbs) this should maybe catch network errors.
296 change = _QueryChange(dep.ToGerritQueryText())[-1]
297 if change.status == 'NEW':
298 yield change
299
300 transitives = _BreadthFirstSearch(
301 cls, _Children,
302 visited_key=lambda cl: cl.gerrit_number)
303
304 transitives_raw = [cl.patch_dict for cl in transitives]
305 lims = limits(transitives_raw)
306 for cl in transitives_raw:
307 PrintCl(opts, cl, lims)
308
309
Mike Frysingerd8f841c2014-06-15 00:48:26 -0700310def UserActInspect(opts, *args):
311 """Inspect CL number <n> [n ...]"""
Mike Frysinger88f27292014-06-17 09:40:45 -0700312 for arg in args:
313 cl = FilteredQuery(opts, arg)
Mike Frysingerd8f841c2014-06-15 00:48:26 -0700314 if cl:
315 PrintCl(opts, cl[0], None)
316 else:
Mike Frysinger88f27292014-06-17 09:40:45 -0700317 print('no results found for CL %s' % arg)
Mike Frysinger13f23a42013-05-13 17:32:01 -0400318
319
Mike Frysingerd8f841c2014-06-15 00:48:26 -0700320def UserActReview(opts, *args):
321 """Mark CL <n> [n ...] with code review status <-2,-1,0,1,2>"""
322 num = args[-1]
Mike Frysinger88f27292014-06-17 09:40:45 -0700323 for arg in args[:-1]:
324 helper, cl = GetGerrit(opts, arg)
325 helper.SetReview(cl, labels={'Code-Review': num}, dryrun=opts.dryrun)
Mike Frysingerd8f841c2014-06-15 00:48:26 -0700326UserActReview.arg_min = 2
Mike Frysinger13f23a42013-05-13 17:32:01 -0400327
328
Mike Frysingerd8f841c2014-06-15 00:48:26 -0700329def UserActVerify(opts, *args):
330 """Mark CL <n> [n ...] with verify status <-1,0,1>"""
331 num = args[-1]
Mike Frysinger88f27292014-06-17 09:40:45 -0700332 for arg in args[:-1]:
333 helper, cl = GetGerrit(opts, arg)
334 helper.SetReview(cl, labels={'Verified': num}, dryrun=opts.dryrun)
Mike Frysingerd8f841c2014-06-15 00:48:26 -0700335UserActVerify.arg_min = 2
Mike Frysinger13f23a42013-05-13 17:32:01 -0400336
337
Mike Frysingerd8f841c2014-06-15 00:48:26 -0700338def UserActReady(opts, *args):
339 """Mark CL <n> [n ...] with ready status <0,1,2>"""
340 num = args[-1]
Mike Frysinger88f27292014-06-17 09:40:45 -0700341 for arg in args[:-1]:
342 helper, cl = GetGerrit(opts, arg)
343 helper.SetReview(cl, labels={'Commit-Queue': num}, dryrun=opts.dryrun)
Mike Frysingerd8f841c2014-06-15 00:48:26 -0700344UserActReady.arg_min = 2
Mike Frysinger13f23a42013-05-13 17:32:01 -0400345
346
Mike Frysinger15b23e42014-12-05 17:00:05 -0500347def UserActTrybotready(opts, *args):
348 """Mark CL <n> [n ...] with trybot-ready status <0,1>"""
349 num = args[-1]
350 for arg in args[:-1]:
351 helper, cl = GetGerrit(opts, arg)
352 helper.SetReview(cl, labels={'Trybot-Ready': num}, dryrun=opts.dryrun)
353UserActTrybotready.arg_min = 2
354
355
Mike Frysingerd8f841c2014-06-15 00:48:26 -0700356def UserActSubmit(opts, *args):
357 """Submit CL <n> [n ...]"""
Mike Frysinger88f27292014-06-17 09:40:45 -0700358 for arg in args:
359 helper, cl = GetGerrit(opts, arg)
360 helper.SubmitChange(cl, dryrun=opts.dryrun)
Mike Frysinger13f23a42013-05-13 17:32:01 -0400361
362
Mike Frysingerd8f841c2014-06-15 00:48:26 -0700363def UserActAbandon(opts, *args):
364 """Abandon CL <n> [n ...]"""
Mike Frysinger88f27292014-06-17 09:40:45 -0700365 for arg in args:
366 helper, cl = GetGerrit(opts, arg)
367 helper.AbandonChange(cl, dryrun=opts.dryrun)
Mike Frysinger13f23a42013-05-13 17:32:01 -0400368
369
Mike Frysingerd8f841c2014-06-15 00:48:26 -0700370def UserActRestore(opts, *args):
371 """Restore CL <n> [n ...] that was abandoned"""
Mike Frysinger88f27292014-06-17 09:40:45 -0700372 for arg in args:
373 helper, cl = GetGerrit(opts, arg)
374 helper.RestoreChange(cl, dryrun=opts.dryrun)
Mike Frysinger13f23a42013-05-13 17:32:01 -0400375
376
Mike Frysinger88f27292014-06-17 09:40:45 -0700377def UserActReviewers(opts, cl, *args):
Vadim Bendeburydcfe2322013-05-23 10:54:49 -0700378 """Add/remove reviewers' emails for CL <n> (prepend with '~' to remove)"""
Mike Frysingerc15efa52013-12-12 01:13:56 -0500379 emails = args
Vadim Bendeburydcfe2322013-05-23 10:54:49 -0700380 # Allow for optional leading '~'.
381 email_validator = re.compile(r'^[~]?%s$' % constants.EMAIL_REGEX)
382 add_list, remove_list, invalid_list = [], [], []
383
384 for x in emails:
385 if not email_validator.match(x):
386 invalid_list.append(x)
387 elif x[0] == '~':
388 remove_list.append(x[1:])
389 else:
390 add_list.append(x)
391
392 if invalid_list:
393 cros_build_lib.Die(
394 'Invalid email address(es): %s' % ', '.join(invalid_list))
395
396 if add_list or remove_list:
Mike Frysinger88f27292014-06-17 09:40:45 -0700397 helper, cl = GetGerrit(opts, cl)
398 helper.SetReviewers(cl, add=add_list, remove=remove_list,
399 dryrun=opts.dryrun)
Vadim Bendeburydcfe2322013-05-23 10:54:49 -0700400
401
Mike Frysinger88f27292014-06-17 09:40:45 -0700402def UserActMessage(opts, cl, message):
Doug Anderson8119df02013-07-20 21:00:24 +0530403 """Add a message to CL <n>"""
Mike Frysinger88f27292014-06-17 09:40:45 -0700404 helper, cl = GetGerrit(opts, cl)
405 helper.SetReview(cl, msg=message, dryrun=opts.dryrun)
Doug Anderson8119df02013-07-20 21:00:24 +0530406
407
Mathieu Olivari02f89b32015-01-09 13:53:38 -0800408def UserActTopic(opts, topic, *args):
409 """Set |topic| for CL number <n> [n ...]"""
410 for arg in args:
411 helper, arg = GetGerrit(opts, arg)
412 helper.SetTopic(arg, topic, dryrun=opts.dryrun)
413
414
Mike Frysingerd8f841c2014-06-15 00:48:26 -0700415def UserActDeletedraft(opts, *args):
416 """Delete draft patch set <n> [n ...]"""
Mike Frysinger88f27292014-06-17 09:40:45 -0700417 for arg in args:
418 helper, cl = GetGerrit(opts, arg)
419 helper.DeleteDraft(cl, dryrun=opts.dryrun)
Jon Salza427fb02014-03-07 18:13:17 +0800420
421
Yu-Ju Hongc20d7b32014-11-18 07:51:11 -0800422def UserActAccount(opts):
423 """Get user account information."""
424 helper, _ = GetGerrit(opts)
425 pprint.PrettyPrinter().pprint(helper.GetAccount())
426
427
Mike Frysinger13f23a42013-05-13 17:32:01 -0400428def main(argv):
429 # Locate actions that are exposed to the user. All functions that start
430 # with "UserAct" are fair game.
431 act_pfx = 'UserAct'
432 actions = [x for x in globals() if x.startswith(act_pfx)]
433
Mike Frysingerddf86eb2014-02-07 22:51:41 -0500434 usage = """%(prog)s [options] <action> [action args]
Mike Frysinger13f23a42013-05-13 17:32:01 -0400435
436There is no support for doing line-by-line code review via the command line.
437This helps you manage various bits and CL status.
438
Mike Frysingera1db2c42014-06-15 00:42:48 -0700439For general Gerrit documentation, see:
440 https://gerrit-review.googlesource.com/Documentation/
441The Searching Changes page covers the search query syntax:
442 https://gerrit-review.googlesource.com/Documentation/user-search.html
443
Mike Frysinger13f23a42013-05-13 17:32:01 -0400444Example:
445 $ gerrit todo # List all the CLs that await your review.
446 $ gerrit mine # List all of your open CLs.
447 $ gerrit inspect 28123 # Inspect CL 28123 on the public gerrit.
448 $ gerrit inspect *28123 # Inspect CL 28123 on the internal gerrit.
449 $ gerrit verify 28123 1 # Mark CL 28123 as verified (+1).
Mike Frysingerd8f841c2014-06-15 00:48:26 -0700450Scripting:
Mike Frysinger88f27292014-06-17 09:40:45 -0700451 $ gerrit ready `gerrit --raw mine` 1 # Mark *ALL* of your public CLs \
452ready.
453 $ gerrit ready `gerrit --raw -i mine` 1 # Mark *ALL* of your internal CLs \
454ready.
Mike Frysinger13f23a42013-05-13 17:32:01 -0400455
456Actions:"""
457 indent = max([len(x) - len(act_pfx) for x in actions])
458 for a in sorted(actions):
Mike Frysinger15b23e42014-12-05 17:00:05 -0500459 cmd = a[len(act_pfx):]
460 # Sanity check for devs adding new commands. Should be quick.
461 if cmd != cmd.lower().capitalize():
462 raise RuntimeError('callback "%s" is misnamed; should be "%s"' %
463 (cmd, cmd.lower().capitalize()))
464 usage += '\n %-*s: %s' % (indent, cmd.lower(), globals()[a].__doc__)
Mike Frysinger13f23a42013-05-13 17:32:01 -0400465
Mike Frysingerddf86eb2014-02-07 22:51:41 -0500466 parser = commandline.ArgumentParser(usage=usage)
Mike Frysinger08737512014-02-07 22:58:26 -0500467 parser.add_argument('-i', '--internal', dest='gob', action='store_const',
Matthew Sartorid2e6bdf2015-07-23 12:07:39 -0700468 default=site_config.params.EXTERNAL_GOB_INSTANCE,
469 const=site_config.params.INTERNAL_GOB_INSTANCE,
Mike Frysinger08737512014-02-07 22:58:26 -0500470 help='Query internal Chromium Gerrit instance')
471 parser.add_argument('-g', '--gob',
Matthew Sartorid2e6bdf2015-07-23 12:07:39 -0700472 default=site_config.params.EXTERNAL_GOB_INSTANCE,
Mike Frysingerd6e2df02014-11-26 02:55:04 -0500473 help=('Gerrit (on borg) instance to query (default: %s)' %
Matthew Sartorid2e6bdf2015-07-23 12:07:39 -0700474 (site_config.params.EXTERNAL_GOB_INSTANCE)))
Mike Frysingerddf86eb2014-02-07 22:51:41 -0500475 parser.add_argument('--sort', default='number',
476 help='Key to sort on (number, project)')
Mike Frysingerf70bdc72014-06-15 00:44:06 -0700477 parser.add_argument('--raw', default=False, action='store_true',
478 help='Return raw results (suitable for scripting)')
Mike Frysinger550d9aa2014-06-15 00:55:31 -0700479 parser.add_argument('-n', '--dry-run', default=False, action='store_true',
480 dest='dryrun',
481 help='Show what would be done, but do not make changes')
Mike Frysingerddf86eb2014-02-07 22:51:41 -0500482 parser.add_argument('-v', '--verbose', default=False, action='store_true',
483 help='Be more verbose in output')
Vadim Bendebury6e057b32014-12-29 09:41:36 -0800484 parser.add_argument('-b', '--branch',
485 help='Limit output to the specific branch')
Mathieu Olivariedc45b82015-01-12 19:43:20 -0800486 parser.add_argument('-p', '--project',
487 help='Limit output to the specific project')
Mathieu Olivari14645a12015-01-16 15:41:32 -0800488 parser.add_argument('-t', '--topic',
489 help='Limit output to the specific topic')
Mike Frysingerddf86eb2014-02-07 22:51:41 -0500490 parser.add_argument('args', nargs='+')
491 opts = parser.parse_args(argv)
Mike Frysinger13f23a42013-05-13 17:32:01 -0400492
Mike Frysinger88f27292014-06-17 09:40:45 -0700493 # A cache of gerrit helpers we'll load on demand.
494 opts.gerrit = {}
495 opts.Freeze()
496
Mike Frysinger031ad0b2013-05-14 18:15:34 -0400497 # pylint: disable=W0603
498 global COLOR
499 COLOR = terminal.Color(enabled=opts.color)
500
Mike Frysinger13f23a42013-05-13 17:32:01 -0400501 # Now look up the requested user action and run it.
Mike Frysinger88f27292014-06-17 09:40:45 -0700502 cmd = opts.args[0].lower()
503 args = opts.args[1:]
Mike Frysinger13f23a42013-05-13 17:32:01 -0400504 functor = globals().get(act_pfx + cmd.capitalize())
505 if functor:
506 argspec = inspect.getargspec(functor)
Vadim Bendeburydcfe2322013-05-23 10:54:49 -0700507 if argspec.varargs:
Mike Frysingerd8f841c2014-06-15 00:48:26 -0700508 arg_min = getattr(functor, 'arg_min', len(argspec.args))
509 if len(args) < arg_min:
Vadim Bendeburydcfe2322013-05-23 10:54:49 -0700510 parser.error('incorrect number of args: %s expects at least %s' %
Mike Frysingerd8f841c2014-06-15 00:48:26 -0700511 (cmd, arg_min))
Vadim Bendeburydcfe2322013-05-23 10:54:49 -0700512 elif len(argspec.args) - 1 != len(args):
Mike Frysinger13f23a42013-05-13 17:32:01 -0400513 parser.error('incorrect number of args: %s expects %s' %
514 (cmd, len(argspec.args) - 1))
Vadim Bendebury614f8682013-05-23 10:33:35 -0700515 try:
516 functor(opts, *args)
Mike Frysingerc85d8162014-02-08 00:45:21 -0500517 except (cros_build_lib.RunCommandError, gerrit.GerritException,
518 gob_util.GOBError) as e:
Vadim Bendeburydcfe2322013-05-23 10:54:49 -0700519 cros_build_lib.Die(e.message)
Mike Frysinger13f23a42013-05-13 17:32:01 -0400520 else:
521 parser.error('unknown action: %s' % (cmd,))