blob: bedfc7930ae9fa54cbcf38a77b28995ae2a238f5 [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
Mike Frysingera1b4b272017-04-05 16:11:00 -0400123def PrettyPrintCl(opts, cl, lims=None, show_approvals=True):
Mike Frysinger13f23a42013-05-13 17:32:01 -0400124 """Pretty print a single result"""
Mike Frysingera1b4b272017-04-05 16:11:00 -0400125 if lims is None:
Mike Frysinger13f23a42013-05-13 17:32:01 -0400126 lims = {'url': 0, 'project': 0}
127
128 status = ''
129 if show_approvals and not opts.verbose:
Mike Frysingerb4a3e3c2017-04-05 16:06:53 -0400130 approvs = GetApprovalSummary(opts, cl)
Mike Frysinger13f23a42013-05-13 17:32:01 -0400131 for cat in GERRIT_SUMMARY_CATS:
132 if approvs[cat] is '':
133 functor = lambda x: x
134 elif approvs[cat] < 0:
135 functor = red
136 else:
137 functor = green
138 status += functor('%s:%2s ' % (cat, approvs[cat]))
139
Mike Frysingerb4a3e3c2017-04-05 16:06:53 -0400140 print('%s %s%-*s %s' % (blue('%-*s' % (lims['url'], cl['url'])), status,
141 lims['project'], cl['project'], cl['subject']))
Mike Frysinger13f23a42013-05-13 17:32:01 -0400142
143 if show_approvals and opts.verbose:
Mike Frysingerb4a3e3c2017-04-05 16:06:53 -0400144 for approver in cl['currentPatchSet'].get('approvals', []):
Mike Frysinger13f23a42013-05-13 17:32:01 -0400145 functor = red if int(approver['value']) < 0 else green
146 n = functor('%2s' % approver['value'])
147 t = GERRIT_APPROVAL_MAP.get(approver['type'], [approver['type'],
148 approver['type']])[1]
Mike Frysinger31ff6f92014-02-08 04:33:03 -0500149 print(' %s %s %s' % (n, t, approver['by']['email']))
Mike Frysinger13f23a42013-05-13 17:32:01 -0400150
151
Mike Frysingera1b4b272017-04-05 16:11:00 -0400152def PrintCls(opts, cls, lims=None, show_approvals=True):
153 """Pretty print all results."""
154 if opts.raw:
155 pfx = ''
156 # Special case internal Chrome GoB as that is what most devs use.
157 # They can always redirect the list elsewhere via the -g option.
158 if opts.gob == site_config.params.INTERNAL_GOB_INSTANCE:
159 pfx = site_config.params.INTERNAL_CHANGE_PREFIX
160 for cl in cls:
161 print('%s%s' % (pfx, cl['number']))
162
163 else:
164 if lims is None:
165 lims = limits(cls)
166
167 for cl in cls:
168 PrettyPrintCl(opts, cl, lims=lims, show_approvals=show_approvals)
169
170
Mike Frysinger13f23a42013-05-13 17:32:01 -0400171def _MyUserInfo():
Mike Frysinger2cd56022017-01-12 20:56:27 -0500172 """Try to return e-mail addresses used by the active user."""
173 return [git.GetProjectUserEmail(constants.CHROMITE_DIR)]
Mike Frysinger13f23a42013-05-13 17:32:01 -0400174
175
Paul Hobbs89765232015-06-24 14:07:49 -0700176def _Query(opts, query, raw=True):
177 """Queries Gerrit with a query string built from the commandline options"""
Vadim Bendebury6e057b32014-12-29 09:41:36 -0800178 if opts.branch is not None:
179 query += ' branch:%s' % opts.branch
Mathieu Olivariedc45b82015-01-12 19:43:20 -0800180 if opts.project is not None:
181 query += ' project: %s' % opts.project
Mathieu Olivari14645a12015-01-16 15:41:32 -0800182 if opts.topic is not None:
183 query += ' topic: %s' % opts.topic
Vadim Bendebury6e057b32014-12-29 09:41:36 -0800184
Mike Frysinger88f27292014-06-17 09:40:45 -0700185 helper, _ = GetGerrit(opts)
Paul Hobbs89765232015-06-24 14:07:49 -0700186 return helper.Query(query, raw=raw, bypass_cache=False)
187
188
189def FilteredQuery(opts, query):
190 """Query gerrit and filter/clean up the results"""
191 ret = []
192
Mike Frysinger2cd56022017-01-12 20:56:27 -0500193 logging.debug('Running query: %s', query)
Paul Hobbs89765232015-06-24 14:07:49 -0700194 for cl in _Query(opts, query, raw=True):
Mike Frysinger13f23a42013-05-13 17:32:01 -0400195 # Gerrit likes to return a stats record too.
196 if not 'project' in cl:
197 continue
198
199 # Strip off common leading names since the result is still
200 # unique over the whole tree.
201 if not opts.verbose:
Mike Frysingerc0fc8de2017-04-04 17:49:27 -0400202 for pfx in ('chromeos', 'chromiumos', 'external', 'overlays', 'platform',
Mike Frysingere5e78272014-06-15 00:41:30 -0700203 'third_party'):
Mike Frysinger13f23a42013-05-13 17:32:01 -0400204 if cl['project'].startswith('%s/' % pfx):
205 cl['project'] = cl['project'][len(pfx) + 1:]
206
207 ret.append(cl)
208
Paul Hobbs89765232015-06-24 14:07:49 -0700209 if opts.sort == 'number':
Mike Frysinger13f23a42013-05-13 17:32:01 -0400210 key = lambda x: int(x[opts.sort])
211 else:
212 key = lambda x: x[opts.sort]
213 return sorted(ret, key=key)
214
215
Mike Frysinger13f23a42013-05-13 17:32:01 -0400216def IsApprover(cl, users):
217 """See if the approvers in |cl| is listed in |users|"""
218 # See if we are listed in the approvals list. We have to parse
219 # this by hand as the gerrit query system doesn't support it :(
220 # http://code.google.com/p/gerrit/issues/detail?id=1235
221 if 'approvals' not in cl['currentPatchSet']:
222 return False
223
224 if isinstance(users, basestring):
225 users = (users,)
226
227 for approver in cl['currentPatchSet']['approvals']:
Stefan Zager29560302013-09-06 14:30:54 -0700228 if (approver['by']['email'] in users and
229 approver['type'] == 'CRVW' and
230 int(approver['value']) != 0):
Mike Frysinger13f23a42013-05-13 17:32:01 -0400231 return True
232
233 return False
234
235
236def UserActTodo(opts):
237 """List CLs needing your review"""
Mike Frysinger2cd56022017-01-12 20:56:27 -0500238 emails = _MyUserInfo()
239 cls = FilteredQuery(opts, 'reviewer:self status:open NOT owner:self')
Mike Frysinger13f23a42013-05-13 17:32:01 -0400240 cls = [x for x in cls if not IsApprover(x, emails)]
Mike Frysingera1b4b272017-04-05 16:11:00 -0400241 PrintCls(opts, cls)
Mike Frysinger13f23a42013-05-13 17:32:01 -0400242
243
Mike Frysingera1db2c42014-06-15 00:42:48 -0700244def UserActSearch(opts, query):
245 """List CLs matching the Gerrit <search query>"""
246 cls = FilteredQuery(opts, query)
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 UserActMine(opts):
251 """List your CLs with review statuses"""
Vadim Bendebury0278a7e2015-09-05 15:23:13 -0700252 if opts.draft:
253 rule = 'is:draft'
254 else:
255 rule = 'status:new'
Mike Frysinger2cd56022017-01-12 20:56:27 -0500256 UserActSearch(opts, 'owner:self %s' % (rule,))
Mike Frysingera1db2c42014-06-15 00:42:48 -0700257
258
Paul Hobbs89765232015-06-24 14:07:49 -0700259def _BreadthFirstSearch(to_visit, children, visited_key=lambda x: x):
260 """Runs breadth first search starting from the nodes in |to_visit|
261
262 Args:
263 to_visit: the starting nodes
264 children: a function which takes a node and returns the nodes adjacent to it
265 visited_key: a function for deduplicating node visits. Defaults to the
266 identity function (lambda x: x)
267
268 Returns:
269 A list of nodes which are reachable from any node in |to_visit| by calling
270 |children| any number of times.
271 """
272 to_visit = list(to_visit)
273 seen = set(map(visited_key, to_visit))
274 for node in to_visit:
275 for child in children(node):
276 key = visited_key(child)
277 if key not in seen:
278 seen.add(key)
279 to_visit.append(child)
280 return to_visit
281
282
283def UserActDeps(opts, query):
284 """List CLs matching a query, and all transitive dependencies of those CLs"""
285 cls = _Query(opts, query, raw=False)
286
287 @cros_build_lib.Memoize
288 def _QueryChange(cl):
289 return _Query(opts, cl, raw=False)
290
291 def _Children(cl):
292 """Returns the Gerrit and CQ-Depends dependencies of a patch"""
293 cq_deps = cl.PaladinDependencies(None)
294 direct_deps = cl.GerritDependencies() + cq_deps
295 # We need to query the change to guarantee that we have a .gerrit_number
296 for dep in direct_deps:
297 # TODO(phobbs) this should maybe catch network errors.
298 change = _QueryChange(dep.ToGerritQueryText())[-1]
299 if change.status == 'NEW':
300 yield change
301
302 transitives = _BreadthFirstSearch(
303 cls, _Children,
304 visited_key=lambda cl: cl.gerrit_number)
305
306 transitives_raw = [cl.patch_dict for cl in transitives]
Mike Frysingera1b4b272017-04-05 16:11:00 -0400307 PrintCls(opts, transitives_raw)
Paul Hobbs89765232015-06-24 14:07:49 -0700308
309
Mike Frysingerd8f841c2014-06-15 00:48:26 -0700310def UserActInspect(opts, *args):
311 """Inspect CL number <n> [n ...]"""
Mike Frysingera1b4b272017-04-05 16:11:00 -0400312 cls = []
Mike Frysinger88f27292014-06-17 09:40:45 -0700313 for arg in args:
314 cl = FilteredQuery(opts, arg)
Mike Frysingerd8f841c2014-06-15 00:48:26 -0700315 if cl:
Mike Frysingera1b4b272017-04-05 16:11:00 -0400316 cls.extend(cl)
Mike Frysingerd8f841c2014-06-15 00:48:26 -0700317 else:
Mike Frysingera1b4b272017-04-05 16:11:00 -0400318 logging.warning('no results found for CL %s', arg)
319 PrintCls(opts, cls)
Mike Frysinger13f23a42013-05-13 17:32:01 -0400320
321
Mike Frysingerd8f841c2014-06-15 00:48:26 -0700322def UserActReview(opts, *args):
323 """Mark CL <n> [n ...] with code review status <-2,-1,0,1,2>"""
324 num = args[-1]
Mike Frysinger88f27292014-06-17 09:40:45 -0700325 for arg in args[:-1]:
326 helper, cl = GetGerrit(opts, arg)
327 helper.SetReview(cl, labels={'Code-Review': num}, dryrun=opts.dryrun)
Mike Frysingerd8f841c2014-06-15 00:48:26 -0700328UserActReview.arg_min = 2
Mike Frysinger13f23a42013-05-13 17:32:01 -0400329
330
Mike Frysingerd8f841c2014-06-15 00:48:26 -0700331def UserActVerify(opts, *args):
332 """Mark CL <n> [n ...] with verify status <-1,0,1>"""
333 num = args[-1]
Mike Frysinger88f27292014-06-17 09:40:45 -0700334 for arg in args[:-1]:
335 helper, cl = GetGerrit(opts, arg)
336 helper.SetReview(cl, labels={'Verified': num}, dryrun=opts.dryrun)
Mike Frysingerd8f841c2014-06-15 00:48:26 -0700337UserActVerify.arg_min = 2
Mike Frysinger13f23a42013-05-13 17:32:01 -0400338
339
Mike Frysingerd8f841c2014-06-15 00:48:26 -0700340def UserActReady(opts, *args):
341 """Mark CL <n> [n ...] with ready status <0,1,2>"""
342 num = args[-1]
Mike Frysinger88f27292014-06-17 09:40:45 -0700343 for arg in args[:-1]:
344 helper, cl = GetGerrit(opts, arg)
345 helper.SetReview(cl, labels={'Commit-Queue': num}, dryrun=opts.dryrun)
Mike Frysingerd8f841c2014-06-15 00:48:26 -0700346UserActReady.arg_min = 2
Mike Frysinger13f23a42013-05-13 17:32:01 -0400347
348
Mike Frysinger15b23e42014-12-05 17:00:05 -0500349def UserActTrybotready(opts, *args):
350 """Mark CL <n> [n ...] with trybot-ready status <0,1>"""
351 num = args[-1]
352 for arg in args[:-1]:
353 helper, cl = GetGerrit(opts, arg)
354 helper.SetReview(cl, labels={'Trybot-Ready': num}, dryrun=opts.dryrun)
355UserActTrybotready.arg_min = 2
356
357
Mike Frysingerd8f841c2014-06-15 00:48:26 -0700358def UserActSubmit(opts, *args):
359 """Submit CL <n> [n ...]"""
Mike Frysinger88f27292014-06-17 09:40:45 -0700360 for arg in args:
361 helper, cl = GetGerrit(opts, arg)
362 helper.SubmitChange(cl, dryrun=opts.dryrun)
Mike Frysinger13f23a42013-05-13 17:32:01 -0400363
364
Mike Frysingerd8f841c2014-06-15 00:48:26 -0700365def UserActAbandon(opts, *args):
366 """Abandon CL <n> [n ...]"""
Mike Frysinger88f27292014-06-17 09:40:45 -0700367 for arg in args:
368 helper, cl = GetGerrit(opts, arg)
369 helper.AbandonChange(cl, dryrun=opts.dryrun)
Mike Frysinger13f23a42013-05-13 17:32:01 -0400370
371
Mike Frysingerd8f841c2014-06-15 00:48:26 -0700372def UserActRestore(opts, *args):
373 """Restore CL <n> [n ...] that was abandoned"""
Mike Frysinger88f27292014-06-17 09:40:45 -0700374 for arg in args:
375 helper, cl = GetGerrit(opts, arg)
376 helper.RestoreChange(cl, dryrun=opts.dryrun)
Mike Frysinger13f23a42013-05-13 17:32:01 -0400377
378
Mike Frysinger88f27292014-06-17 09:40:45 -0700379def UserActReviewers(opts, cl, *args):
Vadim Bendeburydcfe2322013-05-23 10:54:49 -0700380 """Add/remove reviewers' emails for CL <n> (prepend with '~' to remove)"""
Mike Frysingerc15efa52013-12-12 01:13:56 -0500381 emails = args
Vadim Bendeburydcfe2322013-05-23 10:54:49 -0700382 # Allow for optional leading '~'.
383 email_validator = re.compile(r'^[~]?%s$' % constants.EMAIL_REGEX)
384 add_list, remove_list, invalid_list = [], [], []
385
386 for x in emails:
387 if not email_validator.match(x):
388 invalid_list.append(x)
389 elif x[0] == '~':
390 remove_list.append(x[1:])
391 else:
392 add_list.append(x)
393
394 if invalid_list:
395 cros_build_lib.Die(
396 'Invalid email address(es): %s' % ', '.join(invalid_list))
397
398 if add_list or remove_list:
Mike Frysinger88f27292014-06-17 09:40:45 -0700399 helper, cl = GetGerrit(opts, cl)
400 helper.SetReviewers(cl, add=add_list, remove=remove_list,
401 dryrun=opts.dryrun)
Vadim Bendeburydcfe2322013-05-23 10:54:49 -0700402
403
Allen Li38abdaa2017-03-16 13:25:02 -0700404def UserActAssign(opts, cl, assignee):
405 """Set assignee for CL <n>"""
406 helper, cl = GetGerrit(opts, cl)
407 helper.SetAssignee(cl, assignee, dryrun=opts.dryrun)
408
409
Mike Frysinger88f27292014-06-17 09:40:45 -0700410def UserActMessage(opts, cl, message):
Doug Anderson8119df02013-07-20 21:00:24 +0530411 """Add a message to CL <n>"""
Mike Frysinger88f27292014-06-17 09:40:45 -0700412 helper, cl = GetGerrit(opts, cl)
413 helper.SetReview(cl, msg=message, dryrun=opts.dryrun)
Doug Anderson8119df02013-07-20 21:00:24 +0530414
415
Mathieu Olivari02f89b32015-01-09 13:53:38 -0800416def UserActTopic(opts, topic, *args):
417 """Set |topic| for CL number <n> [n ...]"""
418 for arg in args:
419 helper, arg = GetGerrit(opts, arg)
420 helper.SetTopic(arg, topic, dryrun=opts.dryrun)
421
422
Wei-Han Chenb4c9af52017-02-09 14:43:22 +0800423def UserActSethashtags(opts, cl, *args):
424 """Add/remove hashtags for CL <n> (prepend with '~' to remove)"""
425 hashtags = args
426 add = []
427 remove = []
428 for hashtag in hashtags:
429 if hashtag.startswith('~'):
430 remove.append(hashtag[1:])
431 else:
432 add.append(hashtag)
433 helper, cl = GetGerrit(opts, cl)
434 helper.SetHashtags(cl, add, remove, dryrun=opts.dryrun)
435
436
Mike Frysingerd8f841c2014-06-15 00:48:26 -0700437def UserActDeletedraft(opts, *args):
Marc Herbert02448c82015-10-07 14:03:34 -0700438 """Delete draft CL <n> [n ...]"""
Mike Frysinger88f27292014-06-17 09:40:45 -0700439 for arg in args:
440 helper, cl = GetGerrit(opts, arg)
441 helper.DeleteDraft(cl, dryrun=opts.dryrun)
Jon Salza427fb02014-03-07 18:13:17 +0800442
443
Yu-Ju Hongc20d7b32014-11-18 07:51:11 -0800444def UserActAccount(opts):
445 """Get user account information."""
446 helper, _ = GetGerrit(opts)
447 pprint.PrettyPrinter().pprint(helper.GetAccount())
448
449
Mike Frysinger13f23a42013-05-13 17:32:01 -0400450def main(argv):
451 # Locate actions that are exposed to the user. All functions that start
452 # with "UserAct" are fair game.
453 act_pfx = 'UserAct'
454 actions = [x for x in globals() if x.startswith(act_pfx)]
455
Mike Frysingerddf86eb2014-02-07 22:51:41 -0500456 usage = """%(prog)s [options] <action> [action args]
Mike Frysinger13f23a42013-05-13 17:32:01 -0400457
458There is no support for doing line-by-line code review via the command line.
459This helps you manage various bits and CL status.
460
Mike Frysingera1db2c42014-06-15 00:42:48 -0700461For general Gerrit documentation, see:
462 https://gerrit-review.googlesource.com/Documentation/
463The Searching Changes page covers the search query syntax:
464 https://gerrit-review.googlesource.com/Documentation/user-search.html
465
Mike Frysinger13f23a42013-05-13 17:32:01 -0400466Example:
467 $ gerrit todo # List all the CLs that await your review.
468 $ gerrit mine # List all of your open CLs.
469 $ gerrit inspect 28123 # Inspect CL 28123 on the public gerrit.
470 $ gerrit inspect *28123 # Inspect CL 28123 on the internal gerrit.
471 $ gerrit verify 28123 1 # Mark CL 28123 as verified (+1).
Mike Frysingerd8f841c2014-06-15 00:48:26 -0700472Scripting:
Mike Frysinger88f27292014-06-17 09:40:45 -0700473 $ gerrit ready `gerrit --raw mine` 1 # Mark *ALL* of your public CLs \
474ready.
475 $ gerrit ready `gerrit --raw -i mine` 1 # Mark *ALL* of your internal CLs \
476ready.
Mike Frysinger13f23a42013-05-13 17:32:01 -0400477
478Actions:"""
479 indent = max([len(x) - len(act_pfx) for x in actions])
480 for a in sorted(actions):
Mike Frysinger15b23e42014-12-05 17:00:05 -0500481 cmd = a[len(act_pfx):]
482 # Sanity check for devs adding new commands. Should be quick.
483 if cmd != cmd.lower().capitalize():
484 raise RuntimeError('callback "%s" is misnamed; should be "%s"' %
485 (cmd, cmd.lower().capitalize()))
486 usage += '\n %-*s: %s' % (indent, cmd.lower(), globals()[a].__doc__)
Mike Frysinger13f23a42013-05-13 17:32:01 -0400487
Mike Frysingerddf86eb2014-02-07 22:51:41 -0500488 parser = commandline.ArgumentParser(usage=usage)
Mike Frysinger08737512014-02-07 22:58:26 -0500489 parser.add_argument('-i', '--internal', dest='gob', action='store_const',
Matthew Sartorid2e6bdf2015-07-23 12:07:39 -0700490 default=site_config.params.EXTERNAL_GOB_INSTANCE,
491 const=site_config.params.INTERNAL_GOB_INSTANCE,
Mike Frysinger08737512014-02-07 22:58:26 -0500492 help='Query internal Chromium Gerrit instance')
493 parser.add_argument('-g', '--gob',
Matthew Sartorid2e6bdf2015-07-23 12:07:39 -0700494 default=site_config.params.EXTERNAL_GOB_INSTANCE,
Mike Frysingerd6e2df02014-11-26 02:55:04 -0500495 help=('Gerrit (on borg) instance to query (default: %s)' %
Matthew Sartorid2e6bdf2015-07-23 12:07:39 -0700496 (site_config.params.EXTERNAL_GOB_INSTANCE)))
Mike Frysingerddf86eb2014-02-07 22:51:41 -0500497 parser.add_argument('--sort', default='number',
498 help='Key to sort on (number, project)')
Mike Frysingerf70bdc72014-06-15 00:44:06 -0700499 parser.add_argument('--raw', default=False, action='store_true',
500 help='Return raw results (suitable for scripting)')
Mike Frysinger550d9aa2014-06-15 00:55:31 -0700501 parser.add_argument('-n', '--dry-run', default=False, action='store_true',
502 dest='dryrun',
503 help='Show what would be done, but do not make changes')
Mike Frysingerddf86eb2014-02-07 22:51:41 -0500504 parser.add_argument('-v', '--verbose', default=False, action='store_true',
505 help='Be more verbose in output')
Vadim Bendebury6e057b32014-12-29 09:41:36 -0800506 parser.add_argument('-b', '--branch',
507 help='Limit output to the specific branch')
Vadim Bendebury0278a7e2015-09-05 15:23:13 -0700508 parser.add_argument('--draft', default=False, action='store_true',
509 help="Show draft changes (applicable to 'mine' only)")
Mathieu Olivariedc45b82015-01-12 19:43:20 -0800510 parser.add_argument('-p', '--project',
511 help='Limit output to the specific project')
Mathieu Olivari14645a12015-01-16 15:41:32 -0800512 parser.add_argument('-t', '--topic',
513 help='Limit output to the specific topic')
Mike Frysingerddf86eb2014-02-07 22:51:41 -0500514 parser.add_argument('args', nargs='+')
515 opts = parser.parse_args(argv)
Mike Frysinger13f23a42013-05-13 17:32:01 -0400516
Mike Frysinger88f27292014-06-17 09:40:45 -0700517 # A cache of gerrit helpers we'll load on demand.
518 opts.gerrit = {}
519 opts.Freeze()
520
Mike Frysinger031ad0b2013-05-14 18:15:34 -0400521 # pylint: disable=W0603
522 global COLOR
523 COLOR = terminal.Color(enabled=opts.color)
524
Mike Frysinger13f23a42013-05-13 17:32:01 -0400525 # Now look up the requested user action and run it.
Mike Frysinger88f27292014-06-17 09:40:45 -0700526 cmd = opts.args[0].lower()
527 args = opts.args[1:]
Mike Frysinger13f23a42013-05-13 17:32:01 -0400528 functor = globals().get(act_pfx + cmd.capitalize())
529 if functor:
530 argspec = inspect.getargspec(functor)
Vadim Bendeburydcfe2322013-05-23 10:54:49 -0700531 if argspec.varargs:
Mike Frysingerd8f841c2014-06-15 00:48:26 -0700532 arg_min = getattr(functor, 'arg_min', len(argspec.args))
533 if len(args) < arg_min:
Vadim Bendeburydcfe2322013-05-23 10:54:49 -0700534 parser.error('incorrect number of args: %s expects at least %s' %
Mike Frysingerd8f841c2014-06-15 00:48:26 -0700535 (cmd, arg_min))
Vadim Bendeburydcfe2322013-05-23 10:54:49 -0700536 elif len(argspec.args) - 1 != len(args):
Mike Frysinger13f23a42013-05-13 17:32:01 -0400537 parser.error('incorrect number of args: %s expects %s' %
538 (cmd, len(argspec.args) - 1))
Vadim Bendebury614f8682013-05-23 10:33:35 -0700539 try:
540 functor(opts, *args)
Mike Frysingerc85d8162014-02-08 00:45:21 -0500541 except (cros_build_lib.RunCommandError, gerrit.GerritException,
542 gob_util.GOBError) as e:
Vadim Bendeburydcfe2322013-05-23 10:54:49 -0700543 cros_build_lib.Die(e.message)
Mike Frysinger13f23a42013-05-13 17:32:01 -0400544 else:
545 parser.error('unknown action: %s' % (cmd,))