blob: 8d11679d71b1bfea7d20bc319a1eec0fb9b55220 [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
Mike Frysinger87c74ce2017-04-04 16:12:31 -040015import json
Vadim Bendeburydcfe2322013-05-23 10:54:49 -070016import re
Mike Frysinger87c74ce2017-04-04 16:12:31 -040017import sys
Mike Frysinger13f23a42013-05-13 17:32:01 -040018
Aviv Keshetb7519e12016-10-04 00:50:00 -070019from chromite.lib import config_lib
20from chromite.lib import constants
Mike Frysinger13f23a42013-05-13 17:32:01 -040021from chromite.lib import commandline
22from chromite.lib import cros_build_lib
Ralph Nathan446aee92015-03-23 14:44:56 -070023from chromite.lib import cros_logging as logging
Mike Frysinger13f23a42013-05-13 17:32:01 -040024from chromite.lib import gerrit
Mathieu Olivari04b4d522014-12-18 17:26:34 -080025from chromite.lib import git
Mike Frysingerc85d8162014-02-08 00:45:21 -050026from chromite.lib import gob_util
Mike Frysinger13f23a42013-05-13 17:32:01 -040027from chromite.lib import terminal
28
29
Matthew Sartorid2e6bdf2015-07-23 12:07:39 -070030site_config = config_lib.GetConfig()
31
32
Mike Frysinger031ad0b2013-05-14 18:15:34 -040033COLOR = None
Mike Frysinger13f23a42013-05-13 17:32:01 -040034
35# Map the internal names to the ones we normally show on the web ui.
36GERRIT_APPROVAL_MAP = {
Vadim Bendebury50571832013-11-12 10:43:19 -080037 'COMR': ['CQ', 'Commit Queue ',],
38 'CRVW': ['CR', 'Code Review ',],
39 'SUBM': ['S ', 'Submitted ',],
David James2b2e2c52014-12-02 19:32:07 -080040 'TRY': ['T ', 'Trybot Ready ',],
Vadim Bendebury50571832013-11-12 10:43:19 -080041 'VRIF': ['V ', 'Verified ',],
Mike Frysinger13f23a42013-05-13 17:32:01 -040042}
43
44# Order is important -- matches the web ui. This also controls the short
45# entries that we summarize in non-verbose mode.
46GERRIT_SUMMARY_CATS = ('CR', 'CQ', 'V',)
47
48
49def red(s):
50 return COLOR.Color(terminal.Color.RED, s)
51
52
53def green(s):
54 return COLOR.Color(terminal.Color.GREEN, s)
55
56
57def blue(s):
58 return COLOR.Color(terminal.Color.BLUE, s)
59
60
61def limits(cls):
62 """Given a dict of fields, calculate the longest string lengths
63
64 This allows you to easily format the output of many results so that the
65 various cols all line up correctly.
66 """
67 lims = {}
68 for cl in cls:
69 for k in cl.keys():
Mike Frysingerf16b8f02013-10-21 22:24:46 -040070 # Use %s rather than str() to avoid codec issues.
71 # We also do this so we can format integers.
72 lims[k] = max(lims.get(k, 0), len('%s' % cl[k]))
Mike Frysinger13f23a42013-05-13 17:32:01 -040073 return lims
74
75
Mike Frysinger88f27292014-06-17 09:40:45 -070076# TODO: This func really needs to be merged into the core gerrit logic.
77def GetGerrit(opts, cl=None):
78 """Auto pick the right gerrit instance based on the |cl|
79
80 Args:
81 opts: The general options object.
82 cl: A CL taking one of the forms: 1234 *1234 chromium:1234
83
84 Returns:
85 A tuple of a gerrit object and a sanitized CL #.
86 """
87 gob = opts.gob
Paul Hobbs89765232015-06-24 14:07:49 -070088 if cl is not None:
Mike Frysinger88f27292014-06-17 09:40:45 -070089 if cl.startswith('*'):
Matthew Sartorid2e6bdf2015-07-23 12:07:39 -070090 gob = site_config.params.INTERNAL_GOB_INSTANCE
Mike Frysinger88f27292014-06-17 09:40:45 -070091 cl = cl[1:]
92 elif ':' in cl:
93 gob, cl = cl.split(':', 1)
94
95 if not gob in opts.gerrit:
96 opts.gerrit[gob] = gerrit.GetGerritHelper(gob=gob, print_cmd=opts.debug)
97
98 return (opts.gerrit[gob], cl)
99
100
Mike Frysinger13f23a42013-05-13 17:32:01 -0400101def GetApprovalSummary(_opts, cls):
102 """Return a dict of the most important approvals"""
103 approvs = dict([(x, '') for x in GERRIT_SUMMARY_CATS])
104 if 'approvals' in cls['currentPatchSet']:
105 for approver in cls['currentPatchSet']['approvals']:
106 cats = GERRIT_APPROVAL_MAP.get(approver['type'])
107 if not cats:
Ralph Nathan446aee92015-03-23 14:44:56 -0700108 logging.warning('unknown gerrit approval type: %s', approver['type'])
Mike Frysinger13f23a42013-05-13 17:32:01 -0400109 continue
110 cat = cats[0].strip()
111 val = int(approver['value'])
112 if not cat in approvs:
113 # Ignore the extended categories in the summary view.
114 continue
115 elif approvs[cat] is '':
116 approvs[cat] = val
117 elif val < 0:
118 approvs[cat] = min(approvs[cat], val)
119 else:
120 approvs[cat] = max(approvs[cat], val)
121 return approvs
122
123
Mike Frysingera1b4b272017-04-05 16:11:00 -0400124def PrettyPrintCl(opts, cl, lims=None, show_approvals=True):
Mike Frysinger13f23a42013-05-13 17:32:01 -0400125 """Pretty print a single result"""
Mike Frysingera1b4b272017-04-05 16:11:00 -0400126 if lims is None:
Mike Frysinger13f23a42013-05-13 17:32:01 -0400127 lims = {'url': 0, 'project': 0}
128
129 status = ''
130 if show_approvals and not opts.verbose:
Mike Frysingerb4a3e3c2017-04-05 16:06:53 -0400131 approvs = GetApprovalSummary(opts, cl)
Mike Frysinger13f23a42013-05-13 17:32:01 -0400132 for cat in GERRIT_SUMMARY_CATS:
133 if approvs[cat] is '':
134 functor = lambda x: x
135 elif approvs[cat] < 0:
136 functor = red
137 else:
138 functor = green
139 status += functor('%s:%2s ' % (cat, approvs[cat]))
140
Mike Frysingerb4a3e3c2017-04-05 16:06:53 -0400141 print('%s %s%-*s %s' % (blue('%-*s' % (lims['url'], cl['url'])), status,
142 lims['project'], cl['project'], cl['subject']))
Mike Frysinger13f23a42013-05-13 17:32:01 -0400143
144 if show_approvals and opts.verbose:
Mike Frysingerb4a3e3c2017-04-05 16:06:53 -0400145 for approver in cl['currentPatchSet'].get('approvals', []):
Mike Frysinger13f23a42013-05-13 17:32:01 -0400146 functor = red if int(approver['value']) < 0 else green
147 n = functor('%2s' % approver['value'])
148 t = GERRIT_APPROVAL_MAP.get(approver['type'], [approver['type'],
149 approver['type']])[1]
Mike Frysinger31ff6f92014-02-08 04:33:03 -0500150 print(' %s %s %s' % (n, t, approver['by']['email']))
Mike Frysinger13f23a42013-05-13 17:32:01 -0400151
152
Mike Frysingera1b4b272017-04-05 16:11:00 -0400153def PrintCls(opts, cls, lims=None, show_approvals=True):
Mike Frysinger87c74ce2017-04-04 16:12:31 -0400154 """Print all results based on the requested format."""
Mike Frysingera1b4b272017-04-05 16:11:00 -0400155 if opts.raw:
156 pfx = ''
157 # Special case internal Chrome GoB as that is what most devs use.
158 # They can always redirect the list elsewhere via the -g option.
159 if opts.gob == site_config.params.INTERNAL_GOB_INSTANCE:
160 pfx = site_config.params.INTERNAL_CHANGE_PREFIX
161 for cl in cls:
162 print('%s%s' % (pfx, cl['number']))
163
Mike Frysinger87c74ce2017-04-04 16:12:31 -0400164 elif opts.json:
165 json.dump(cls, sys.stdout)
166
Mike Frysingera1b4b272017-04-05 16:11:00 -0400167 else:
168 if lims is None:
169 lims = limits(cls)
170
171 for cl in cls:
172 PrettyPrintCl(opts, cl, lims=lims, show_approvals=show_approvals)
173
174
Mike Frysinger13f23a42013-05-13 17:32:01 -0400175def _MyUserInfo():
Mike Frysinger2cd56022017-01-12 20:56:27 -0500176 """Try to return e-mail addresses used by the active user."""
177 return [git.GetProjectUserEmail(constants.CHROMITE_DIR)]
Mike Frysinger13f23a42013-05-13 17:32:01 -0400178
179
Paul Hobbs89765232015-06-24 14:07:49 -0700180def _Query(opts, query, raw=True):
181 """Queries Gerrit with a query string built from the commandline options"""
Vadim Bendebury6e057b32014-12-29 09:41:36 -0800182 if opts.branch is not None:
183 query += ' branch:%s' % opts.branch
Mathieu Olivariedc45b82015-01-12 19:43:20 -0800184 if opts.project is not None:
185 query += ' project: %s' % opts.project
Mathieu Olivari14645a12015-01-16 15:41:32 -0800186 if opts.topic is not None:
187 query += ' topic: %s' % opts.topic
Vadim Bendebury6e057b32014-12-29 09:41:36 -0800188
Mike Frysinger88f27292014-06-17 09:40:45 -0700189 helper, _ = GetGerrit(opts)
Paul Hobbs89765232015-06-24 14:07:49 -0700190 return helper.Query(query, raw=raw, bypass_cache=False)
191
192
193def FilteredQuery(opts, query):
194 """Query gerrit and filter/clean up the results"""
195 ret = []
196
Mike Frysinger2cd56022017-01-12 20:56:27 -0500197 logging.debug('Running query: %s', query)
Paul Hobbs89765232015-06-24 14:07:49 -0700198 for cl in _Query(opts, query, raw=True):
Mike Frysinger13f23a42013-05-13 17:32:01 -0400199 # Gerrit likes to return a stats record too.
200 if not 'project' in cl:
201 continue
202
203 # Strip off common leading names since the result is still
204 # unique over the whole tree.
205 if not opts.verbose:
Mike Frysingerc0fc8de2017-04-04 17:49:27 -0400206 for pfx in ('chromeos', 'chromiumos', 'external', 'overlays', 'platform',
Mike Frysingere5e78272014-06-15 00:41:30 -0700207 'third_party'):
Mike Frysinger13f23a42013-05-13 17:32:01 -0400208 if cl['project'].startswith('%s/' % pfx):
209 cl['project'] = cl['project'][len(pfx) + 1:]
210
211 ret.append(cl)
212
Mike Frysingerb62313a2017-06-30 16:38:58 -0400213 if opts.sort == 'unsorted':
214 return ret
Paul Hobbs89765232015-06-24 14:07:49 -0700215 if opts.sort == 'number':
Mike Frysinger13f23a42013-05-13 17:32:01 -0400216 key = lambda x: int(x[opts.sort])
217 else:
218 key = lambda x: x[opts.sort]
219 return sorted(ret, key=key)
220
221
Mike Frysinger13f23a42013-05-13 17:32:01 -0400222def IsApprover(cl, users):
223 """See if the approvers in |cl| is listed in |users|"""
224 # See if we are listed in the approvals list. We have to parse
225 # this by hand as the gerrit query system doesn't support it :(
226 # http://code.google.com/p/gerrit/issues/detail?id=1235
227 if 'approvals' not in cl['currentPatchSet']:
228 return False
229
230 if isinstance(users, basestring):
231 users = (users,)
232
233 for approver in cl['currentPatchSet']['approvals']:
Stefan Zager29560302013-09-06 14:30:54 -0700234 if (approver['by']['email'] in users and
235 approver['type'] == 'CRVW' and
236 int(approver['value']) != 0):
Mike Frysinger13f23a42013-05-13 17:32:01 -0400237 return True
238
239 return False
240
241
242def UserActTodo(opts):
243 """List CLs needing your review"""
Mike Frysinger2cd56022017-01-12 20:56:27 -0500244 emails = _MyUserInfo()
245 cls = FilteredQuery(opts, 'reviewer:self status:open NOT owner:self')
Mike Frysinger13f23a42013-05-13 17:32:01 -0400246 cls = [x for x in cls if not IsApprover(x, emails)]
Mike Frysingera1b4b272017-04-05 16:11:00 -0400247 PrintCls(opts, cls)
Mike Frysinger13f23a42013-05-13 17:32:01 -0400248
249
Mike Frysingera1db2c42014-06-15 00:42:48 -0700250def UserActSearch(opts, query):
251 """List CLs matching the Gerrit <search query>"""
252 cls = FilteredQuery(opts, query)
Mike Frysingera1b4b272017-04-05 16:11:00 -0400253 PrintCls(opts, cls)
Mike Frysinger13f23a42013-05-13 17:32:01 -0400254
255
Mike Frysingera1db2c42014-06-15 00:42:48 -0700256def UserActMine(opts):
257 """List your CLs with review statuses"""
Vadim Bendebury0278a7e2015-09-05 15:23:13 -0700258 if opts.draft:
259 rule = 'is:draft'
260 else:
261 rule = 'status:new'
Mike Frysinger2cd56022017-01-12 20:56:27 -0500262 UserActSearch(opts, 'owner:self %s' % (rule,))
Mike Frysingera1db2c42014-06-15 00:42:48 -0700263
264
Paul Hobbs89765232015-06-24 14:07:49 -0700265def _BreadthFirstSearch(to_visit, children, visited_key=lambda x: x):
266 """Runs breadth first search starting from the nodes in |to_visit|
267
268 Args:
269 to_visit: the starting nodes
270 children: a function which takes a node and returns the nodes adjacent to it
271 visited_key: a function for deduplicating node visits. Defaults to the
272 identity function (lambda x: x)
273
274 Returns:
275 A list of nodes which are reachable from any node in |to_visit| by calling
276 |children| any number of times.
277 """
278 to_visit = list(to_visit)
279 seen = set(map(visited_key, to_visit))
280 for node in to_visit:
281 for child in children(node):
282 key = visited_key(child)
283 if key not in seen:
284 seen.add(key)
285 to_visit.append(child)
286 return to_visit
287
288
289def UserActDeps(opts, query):
290 """List CLs matching a query, and all transitive dependencies of those CLs"""
291 cls = _Query(opts, query, raw=False)
292
293 @cros_build_lib.Memoize
294 def _QueryChange(cl):
295 return _Query(opts, cl, raw=False)
296
297 def _Children(cl):
298 """Returns the Gerrit and CQ-Depends dependencies of a patch"""
299 cq_deps = cl.PaladinDependencies(None)
300 direct_deps = cl.GerritDependencies() + cq_deps
301 # We need to query the change to guarantee that we have a .gerrit_number
302 for dep in direct_deps:
303 # TODO(phobbs) this should maybe catch network errors.
304 change = _QueryChange(dep.ToGerritQueryText())[-1]
305 if change.status == 'NEW':
306 yield change
307
308 transitives = _BreadthFirstSearch(
309 cls, _Children,
310 visited_key=lambda cl: cl.gerrit_number)
311
312 transitives_raw = [cl.patch_dict for cl in transitives]
Mike Frysingera1b4b272017-04-05 16:11:00 -0400313 PrintCls(opts, transitives_raw)
Paul Hobbs89765232015-06-24 14:07:49 -0700314
315
Mike Frysingerd8f841c2014-06-15 00:48:26 -0700316def UserActInspect(opts, *args):
317 """Inspect CL number <n> [n ...]"""
Mike Frysingera1b4b272017-04-05 16:11:00 -0400318 cls = []
Mike Frysinger88f27292014-06-17 09:40:45 -0700319 for arg in args:
320 cl = FilteredQuery(opts, arg)
Mike Frysingerd8f841c2014-06-15 00:48:26 -0700321 if cl:
Mike Frysingera1b4b272017-04-05 16:11:00 -0400322 cls.extend(cl)
Mike Frysingerd8f841c2014-06-15 00:48:26 -0700323 else:
Mike Frysingera1b4b272017-04-05 16:11:00 -0400324 logging.warning('no results found for CL %s', arg)
325 PrintCls(opts, cls)
Mike Frysinger13f23a42013-05-13 17:32:01 -0400326
327
Mike Frysingerd8f841c2014-06-15 00:48:26 -0700328def UserActReview(opts, *args):
329 """Mark CL <n> [n ...] with code review status <-2,-1,0,1,2>"""
330 num = args[-1]
Mike Frysinger88f27292014-06-17 09:40:45 -0700331 for arg in args[:-1]:
332 helper, cl = GetGerrit(opts, arg)
333 helper.SetReview(cl, labels={'Code-Review': num}, dryrun=opts.dryrun)
Mike Frysingerd8f841c2014-06-15 00:48:26 -0700334UserActReview.arg_min = 2
Mike Frysinger13f23a42013-05-13 17:32:01 -0400335
336
Mike Frysingerd8f841c2014-06-15 00:48:26 -0700337def UserActVerify(opts, *args):
338 """Mark CL <n> [n ...] with verify status <-1,0,1>"""
339 num = args[-1]
Mike Frysinger88f27292014-06-17 09:40:45 -0700340 for arg in args[:-1]:
341 helper, cl = GetGerrit(opts, arg)
342 helper.SetReview(cl, labels={'Verified': num}, dryrun=opts.dryrun)
Mike Frysingerd8f841c2014-06-15 00:48:26 -0700343UserActVerify.arg_min = 2
Mike Frysinger13f23a42013-05-13 17:32:01 -0400344
345
Mike Frysingerd8f841c2014-06-15 00:48:26 -0700346def UserActReady(opts, *args):
Kirtika Ruchandanica852f42017-05-23 18:18:05 -0700347 """Mark CL <n> [n ...] with ready status <0,1>"""
Mike Frysingerd8f841c2014-06-15 00:48:26 -0700348 num = args[-1]
Mike Frysinger88f27292014-06-17 09:40:45 -0700349 for arg in args[:-1]:
350 helper, cl = GetGerrit(opts, arg)
351 helper.SetReview(cl, labels={'Commit-Queue': num}, dryrun=opts.dryrun)
Mike Frysingerd8f841c2014-06-15 00:48:26 -0700352UserActReady.arg_min = 2
Mike Frysinger13f23a42013-05-13 17:32:01 -0400353
354
Mike Frysinger15b23e42014-12-05 17:00:05 -0500355def UserActTrybotready(opts, *args):
356 """Mark CL <n> [n ...] with trybot-ready status <0,1>"""
357 num = args[-1]
358 for arg in args[:-1]:
359 helper, cl = GetGerrit(opts, arg)
360 helper.SetReview(cl, labels={'Trybot-Ready': num}, dryrun=opts.dryrun)
361UserActTrybotready.arg_min = 2
362
363
Mike Frysingerd8f841c2014-06-15 00:48:26 -0700364def UserActSubmit(opts, *args):
365 """Submit CL <n> [n ...]"""
Mike Frysinger88f27292014-06-17 09:40:45 -0700366 for arg in args:
367 helper, cl = GetGerrit(opts, arg)
368 helper.SubmitChange(cl, dryrun=opts.dryrun)
Mike Frysinger13f23a42013-05-13 17:32:01 -0400369
370
Mike Frysingerd8f841c2014-06-15 00:48:26 -0700371def UserActAbandon(opts, *args):
372 """Abandon CL <n> [n ...]"""
Mike Frysinger88f27292014-06-17 09:40:45 -0700373 for arg in args:
374 helper, cl = GetGerrit(opts, arg)
375 helper.AbandonChange(cl, dryrun=opts.dryrun)
Mike Frysinger13f23a42013-05-13 17:32:01 -0400376
377
Mike Frysingerd8f841c2014-06-15 00:48:26 -0700378def UserActRestore(opts, *args):
379 """Restore CL <n> [n ...] that was abandoned"""
Mike Frysinger88f27292014-06-17 09:40:45 -0700380 for arg in args:
381 helper, cl = GetGerrit(opts, arg)
382 helper.RestoreChange(cl, dryrun=opts.dryrun)
Mike Frysinger13f23a42013-05-13 17:32:01 -0400383
384
Mike Frysinger88f27292014-06-17 09:40:45 -0700385def UserActReviewers(opts, cl, *args):
Vadim Bendeburydcfe2322013-05-23 10:54:49 -0700386 """Add/remove reviewers' emails for CL <n> (prepend with '~' to remove)"""
Mike Frysingerc15efa52013-12-12 01:13:56 -0500387 emails = args
Vadim Bendeburydcfe2322013-05-23 10:54:49 -0700388 # Allow for optional leading '~'.
389 email_validator = re.compile(r'^[~]?%s$' % constants.EMAIL_REGEX)
390 add_list, remove_list, invalid_list = [], [], []
391
392 for x in emails:
393 if not email_validator.match(x):
394 invalid_list.append(x)
395 elif x[0] == '~':
396 remove_list.append(x[1:])
397 else:
398 add_list.append(x)
399
400 if invalid_list:
401 cros_build_lib.Die(
402 'Invalid email address(es): %s' % ', '.join(invalid_list))
403
404 if add_list or remove_list:
Mike Frysinger88f27292014-06-17 09:40:45 -0700405 helper, cl = GetGerrit(opts, cl)
406 helper.SetReviewers(cl, add=add_list, remove=remove_list,
407 dryrun=opts.dryrun)
Vadim Bendeburydcfe2322013-05-23 10:54:49 -0700408
409
Allen Li38abdaa2017-03-16 13:25:02 -0700410def UserActAssign(opts, cl, assignee):
411 """Set assignee for CL <n>"""
412 helper, cl = GetGerrit(opts, cl)
413 helper.SetAssignee(cl, assignee, dryrun=opts.dryrun)
414
415
Mike Frysinger88f27292014-06-17 09:40:45 -0700416def UserActMessage(opts, cl, message):
Doug Anderson8119df02013-07-20 21:00:24 +0530417 """Add a message to CL <n>"""
Mike Frysinger88f27292014-06-17 09:40:45 -0700418 helper, cl = GetGerrit(opts, cl)
419 helper.SetReview(cl, msg=message, dryrun=opts.dryrun)
Doug Anderson8119df02013-07-20 21:00:24 +0530420
421
Mathieu Olivari02f89b32015-01-09 13:53:38 -0800422def UserActTopic(opts, topic, *args):
423 """Set |topic| for CL number <n> [n ...]"""
424 for arg in args:
425 helper, arg = GetGerrit(opts, arg)
426 helper.SetTopic(arg, topic, dryrun=opts.dryrun)
427
428
Wei-Han Chenb4c9af52017-02-09 14:43:22 +0800429def UserActSethashtags(opts, cl, *args):
430 """Add/remove hashtags for CL <n> (prepend with '~' to remove)"""
431 hashtags = args
432 add = []
433 remove = []
434 for hashtag in hashtags:
435 if hashtag.startswith('~'):
436 remove.append(hashtag[1:])
437 else:
438 add.append(hashtag)
439 helper, cl = GetGerrit(opts, cl)
440 helper.SetHashtags(cl, add, remove, dryrun=opts.dryrun)
441
442
Mike Frysingerd8f841c2014-06-15 00:48:26 -0700443def UserActDeletedraft(opts, *args):
Marc Herbert02448c82015-10-07 14:03:34 -0700444 """Delete draft CL <n> [n ...]"""
Mike Frysinger88f27292014-06-17 09:40:45 -0700445 for arg in args:
446 helper, cl = GetGerrit(opts, arg)
447 helper.DeleteDraft(cl, dryrun=opts.dryrun)
Jon Salza427fb02014-03-07 18:13:17 +0800448
449
Yu-Ju Hongc20d7b32014-11-18 07:51:11 -0800450def UserActAccount(opts):
451 """Get user account information."""
452 helper, _ = GetGerrit(opts)
Mike Frysinger87c74ce2017-04-04 16:12:31 -0400453 acct = helper.GetAccount()
454 if opts.json:
455 json.dump(acct, sys.stdout)
456 else:
457 print('account_id:%i %s <%s>' %
458 (acct['_account_id'], acct['name'], acct['email']))
Yu-Ju Hongc20d7b32014-11-18 07:51:11 -0800459
460
Mike Frysinger13f23a42013-05-13 17:32:01 -0400461def main(argv):
462 # Locate actions that are exposed to the user. All functions that start
463 # with "UserAct" are fair game.
464 act_pfx = 'UserAct'
465 actions = [x for x in globals() if x.startswith(act_pfx)]
466
Mike Frysingerddf86eb2014-02-07 22:51:41 -0500467 usage = """%(prog)s [options] <action> [action args]
Mike Frysinger13f23a42013-05-13 17:32:01 -0400468
469There is no support for doing line-by-line code review via the command line.
470This helps you manage various bits and CL status.
471
Mike Frysingera1db2c42014-06-15 00:42:48 -0700472For general Gerrit documentation, see:
473 https://gerrit-review.googlesource.com/Documentation/
474The Searching Changes page covers the search query syntax:
475 https://gerrit-review.googlesource.com/Documentation/user-search.html
476
Mike Frysinger13f23a42013-05-13 17:32:01 -0400477Example:
478 $ gerrit todo # List all the CLs that await your review.
479 $ gerrit mine # List all of your open CLs.
480 $ gerrit inspect 28123 # Inspect CL 28123 on the public gerrit.
481 $ gerrit inspect *28123 # Inspect CL 28123 on the internal gerrit.
482 $ gerrit verify 28123 1 # Mark CL 28123 as verified (+1).
Mike Frysingerd8f841c2014-06-15 00:48:26 -0700483Scripting:
Mike Frysinger88f27292014-06-17 09:40:45 -0700484 $ gerrit ready `gerrit --raw mine` 1 # Mark *ALL* of your public CLs \
485ready.
486 $ gerrit ready `gerrit --raw -i mine` 1 # Mark *ALL* of your internal CLs \
487ready.
Mike Frysinger87c74ce2017-04-04 16:12:31 -0400488 $ gerrit --json search 'assignee:self' # Dump all pending CLs in JSON.
Mike Frysinger13f23a42013-05-13 17:32:01 -0400489
490Actions:"""
491 indent = max([len(x) - len(act_pfx) for x in actions])
492 for a in sorted(actions):
Mike Frysinger15b23e42014-12-05 17:00:05 -0500493 cmd = a[len(act_pfx):]
494 # Sanity check for devs adding new commands. Should be quick.
495 if cmd != cmd.lower().capitalize():
496 raise RuntimeError('callback "%s" is misnamed; should be "%s"' %
497 (cmd, cmd.lower().capitalize()))
498 usage += '\n %-*s: %s' % (indent, cmd.lower(), globals()[a].__doc__)
Mike Frysinger13f23a42013-05-13 17:32:01 -0400499
Mike Frysingerddf86eb2014-02-07 22:51:41 -0500500 parser = commandline.ArgumentParser(usage=usage)
Mike Frysinger08737512014-02-07 22:58:26 -0500501 parser.add_argument('-i', '--internal', dest='gob', action='store_const',
Matthew Sartorid2e6bdf2015-07-23 12:07:39 -0700502 default=site_config.params.EXTERNAL_GOB_INSTANCE,
503 const=site_config.params.INTERNAL_GOB_INSTANCE,
Mike Frysinger08737512014-02-07 22:58:26 -0500504 help='Query internal Chromium Gerrit instance')
505 parser.add_argument('-g', '--gob',
Matthew Sartorid2e6bdf2015-07-23 12:07:39 -0700506 default=site_config.params.EXTERNAL_GOB_INSTANCE,
Mike Frysingerd6e2df02014-11-26 02:55:04 -0500507 help=('Gerrit (on borg) instance to query (default: %s)' %
Matthew Sartorid2e6bdf2015-07-23 12:07:39 -0700508 (site_config.params.EXTERNAL_GOB_INSTANCE)))
Mike Frysingerddf86eb2014-02-07 22:51:41 -0500509 parser.add_argument('--sort', default='number',
Mike Frysingerb62313a2017-06-30 16:38:58 -0400510 help='Key to sort on (number, project); use "unsorted" '
511 'to disable')
Mike Frysingerf70bdc72014-06-15 00:44:06 -0700512 parser.add_argument('--raw', default=False, action='store_true',
513 help='Return raw results (suitable for scripting)')
Mike Frysinger87c74ce2017-04-04 16:12:31 -0400514 parser.add_argument('--json', default=False, action='store_true',
515 help='Return results in JSON (suitable for scripting)')
Mike Frysinger550d9aa2014-06-15 00:55:31 -0700516 parser.add_argument('-n', '--dry-run', default=False, action='store_true',
517 dest='dryrun',
518 help='Show what would be done, but do not make changes')
Mike Frysingerddf86eb2014-02-07 22:51:41 -0500519 parser.add_argument('-v', '--verbose', default=False, action='store_true',
520 help='Be more verbose in output')
Vadim Bendebury6e057b32014-12-29 09:41:36 -0800521 parser.add_argument('-b', '--branch',
522 help='Limit output to the specific branch')
Vadim Bendebury0278a7e2015-09-05 15:23:13 -0700523 parser.add_argument('--draft', default=False, action='store_true',
524 help="Show draft changes (applicable to 'mine' only)")
Mathieu Olivariedc45b82015-01-12 19:43:20 -0800525 parser.add_argument('-p', '--project',
526 help='Limit output to the specific project')
Mathieu Olivari14645a12015-01-16 15:41:32 -0800527 parser.add_argument('-t', '--topic',
528 help='Limit output to the specific topic')
Mike Frysingerddf86eb2014-02-07 22:51:41 -0500529 parser.add_argument('args', nargs='+')
530 opts = parser.parse_args(argv)
Mike Frysinger13f23a42013-05-13 17:32:01 -0400531
Mike Frysinger88f27292014-06-17 09:40:45 -0700532 # A cache of gerrit helpers we'll load on demand.
533 opts.gerrit = {}
534 opts.Freeze()
535
Mike Frysinger031ad0b2013-05-14 18:15:34 -0400536 # pylint: disable=W0603
537 global COLOR
538 COLOR = terminal.Color(enabled=opts.color)
539
Mike Frysinger13f23a42013-05-13 17:32:01 -0400540 # Now look up the requested user action and run it.
Mike Frysinger88f27292014-06-17 09:40:45 -0700541 cmd = opts.args[0].lower()
542 args = opts.args[1:]
Mike Frysinger13f23a42013-05-13 17:32:01 -0400543 functor = globals().get(act_pfx + cmd.capitalize())
544 if functor:
545 argspec = inspect.getargspec(functor)
Vadim Bendeburydcfe2322013-05-23 10:54:49 -0700546 if argspec.varargs:
Mike Frysingerd8f841c2014-06-15 00:48:26 -0700547 arg_min = getattr(functor, 'arg_min', len(argspec.args))
548 if len(args) < arg_min:
Vadim Bendeburydcfe2322013-05-23 10:54:49 -0700549 parser.error('incorrect number of args: %s expects at least %s' %
Mike Frysingerd8f841c2014-06-15 00:48:26 -0700550 (cmd, arg_min))
Vadim Bendeburydcfe2322013-05-23 10:54:49 -0700551 elif len(argspec.args) - 1 != len(args):
Mike Frysinger13f23a42013-05-13 17:32:01 -0400552 parser.error('incorrect number of args: %s expects %s' %
553 (cmd, len(argspec.args) - 1))
Vadim Bendebury614f8682013-05-23 10:33:35 -0700554 try:
555 functor(opts, *args)
Mike Frysingerc85d8162014-02-08 00:45:21 -0500556 except (cros_build_lib.RunCommandError, gerrit.GerritException,
557 gob_util.GOBError) as e:
Vadim Bendeburydcfe2322013-05-23 10:54:49 -0700558 cros_build_lib.Die(e.message)
Mike Frysinger13f23a42013-05-13 17:32:01 -0400559 else:
560 parser.error('unknown action: %s' % (cmd,))