blob: 2291df01b5f2592ba31579f49880491abc27bee1 [file] [log] [blame]
Josip Sokcevic84434e82021-06-09 22:48:43 +00001#!/usr/bin/env vpython3
dimu833c94c2017-01-18 17:36:15 -08002# Copyright 2017 The Chromium Authors. All rights reserved.
3# Use of this source code is governed by a BSD-style license that can be
4# found in the LICENSE file.
5
6"""Simple client for the Gerrit REST API.
7
8Example usage:
Michael Moss5eebf6f2021-07-16 13:18:34 +00009 ./gerrit_client.py [command] [args]
dimu833c94c2017-01-18 17:36:15 -080010"""
11
12from __future__ import print_function
13
14import json
15import logging
16import optparse
17import subcommand
18import sys
Josip Sokcevicc99efb22020-03-17 00:35:34 +000019
20if sys.version_info.major == 2:
21 import urlparse
22 from urllib import quote_plus
23else:
24 from urllib.parse import quote_plus
25 import urllib.parse as urlparse
dimu833c94c2017-01-18 17:36:15 -080026
dimu833c94c2017-01-18 17:36:15 -080027import fix_encoding
28import gerrit_util
29import setup_color
30
31__version__ = '0.1'
dimu833c94c2017-01-18 17:36:15 -080032
33
34def write_result(result, opt):
35 if opt.json_file:
36 with open(opt.json_file, 'w') as json_file:
37 json_file.write(json.dumps(result))
38
39
40@subcommand.usage('[args ...]')
Josip Sokcevicc39ab992020-09-24 20:09:15 +000041def CMDmovechanges(parser, args):
Michael Moss5eebf6f2021-07-16 13:18:34 +000042 """Move changes to a different destination branch."""
Josip Sokcevicc39ab992020-09-24 20:09:15 +000043 parser.add_option('-p', '--param', dest='params', action='append',
44 help='repeatable query parameter, format: -p key=value')
45 parser.add_option('--destination_branch', dest='destination_branch',
46 help='where to move changes to')
47
48 (opt, args) = parser.parse_args(args)
49 assert opt.destination_branch, "--destination_branch not defined"
Mike Frysinger8820ab82020-11-25 00:52:31 +000050 for p in opt.params:
51 assert '=' in p, '--param is key=value, not "%s"' % p
Josip Sokcevicc39ab992020-09-24 20:09:15 +000052 host = urlparse.urlparse(opt.host).netloc
53
54 limit = 100
55 while True:
56 result = gerrit_util.QueryChanges(
57 host,
58 list(tuple(p.split('=', 1)) for p in opt.params),
59 limit=limit,
60 )
61 for change in result:
62 gerrit_util.MoveChange(host, change['id'], opt.destination_branch)
63
64 if len(result) < limit:
65 break
66 logging.info("Done")
67
68
69@subcommand.usage('[args ...]')
dimu833c94c2017-01-18 17:36:15 -080070def CMDbranchinfo(parser, args):
Michael Moss5eebf6f2021-07-16 13:18:34 +000071 """Get information on a gerrit branch."""
dimu833c94c2017-01-18 17:36:15 -080072 parser.add_option('--branch', dest='branch', help='branch name')
73
74 (opt, args) = parser.parse_args(args)
75 host = urlparse.urlparse(opt.host).netloc
Josip Sokcevicc99efb22020-03-17 00:35:34 +000076 project = quote_plus(opt.project)
77 branch = quote_plus(opt.branch)
dimu833c94c2017-01-18 17:36:15 -080078 result = gerrit_util.GetGerritBranch(host, project, branch)
79 logging.info(result)
80 write_result(result, opt)
81
Quinten Yearsleyd9cbe7a2019-09-03 16:49:11 +000082
dimu833c94c2017-01-18 17:36:15 -080083@subcommand.usage('[args ...]')
84def CMDbranch(parser, args):
Michael Moss5eebf6f2021-07-16 13:18:34 +000085 """Create a branch in a gerrit project."""
dimu833c94c2017-01-18 17:36:15 -080086 parser.add_option('--branch', dest='branch', help='branch name')
87 parser.add_option('--commit', dest='commit', help='commit hash')
88
89 (opt, args) = parser.parse_args(args)
Josip Sokcevicc99efb22020-03-17 00:35:34 +000090 assert opt.project, "--project not defined"
91 assert opt.branch, "--branch not defined"
92 assert opt.commit, "--commit not defined"
dimu833c94c2017-01-18 17:36:15 -080093
Josip Sokcevicc99efb22020-03-17 00:35:34 +000094 project = quote_plus(opt.project)
dimu833c94c2017-01-18 17:36:15 -080095 host = urlparse.urlparse(opt.host).netloc
Josip Sokcevicc99efb22020-03-17 00:35:34 +000096 branch = quote_plus(opt.branch)
97 commit = quote_plus(opt.commit)
dimu833c94c2017-01-18 17:36:15 -080098 result = gerrit_util.CreateGerritBranch(host, project, branch, commit)
99 logging.info(result)
100 write_result(result, opt)
101
102
Michael Achenbach6fbf12f2017-07-06 10:54:11 +0200103@subcommand.usage('[args ...]')
Josip Sokcevicdf9a8022020-12-08 00:10:19 +0000104def CMDhead(parser, args):
Michael Moss5eebf6f2021-07-16 13:18:34 +0000105 """Update which branch the project HEAD points to."""
Josip Sokcevicdf9a8022020-12-08 00:10:19 +0000106 parser.add_option('--branch', dest='branch', help='branch name')
107
108 (opt, args) = parser.parse_args(args)
109 assert opt.project, "--project not defined"
110 assert opt.branch, "--branch not defined"
111
112 project = quote_plus(opt.project)
113 host = urlparse.urlparse(opt.host).netloc
114 branch = quote_plus(opt.branch)
115 result = gerrit_util.UpdateHead(host, project, branch)
116 logging.info(result)
117 write_result(result, opt)
118
119
120@subcommand.usage('[args ...]')
121def CMDheadinfo(parser, args):
Michael Moss5eebf6f2021-07-16 13:18:34 +0000122 """Retrieves the current HEAD of the project."""
Josip Sokcevicdf9a8022020-12-08 00:10:19 +0000123
124 (opt, args) = parser.parse_args(args)
125 assert opt.project, "--project not defined"
126
127 project = quote_plus(opt.project)
128 host = urlparse.urlparse(opt.host).netloc
129 result = gerrit_util.GetHead(host, project)
130 logging.info(result)
131 write_result(result, opt)
132
133
134@subcommand.usage('[args ...]')
Michael Achenbach6fbf12f2017-07-06 10:54:11 +0200135def CMDchanges(parser, args):
Michael Moss5eebf6f2021-07-16 13:18:34 +0000136 """Queries gerrit for matching changes."""
Michael Achenbach6fbf12f2017-07-06 10:54:11 +0200137 parser.add_option('-p', '--param', dest='params', action='append',
138 help='repeatable query parameter, format: -p key=value')
Paweł Hajdan, Jr24025d32017-07-11 16:38:21 +0200139 parser.add_option('-o', '--o-param', dest='o_params', action='append',
140 help='gerrit output parameters, e.g. ALL_REVISIONS')
Michael Achenbach6fbf12f2017-07-06 10:54:11 +0200141 parser.add_option('--limit', dest='limit', type=int,
142 help='maximum number of results to return')
143 parser.add_option('--start', dest='start', type=int,
144 help='how many changes to skip '
145 '(starting with the most recent)')
146
147 (opt, args) = parser.parse_args(args)
Mike Frysinger8820ab82020-11-25 00:52:31 +0000148 for p in opt.params:
149 assert '=' in p, '--param is key=value, not "%s"' % p
Michael Achenbach6fbf12f2017-07-06 10:54:11 +0200150
151 result = gerrit_util.QueryChanges(
152 urlparse.urlparse(opt.host).netloc,
153 list(tuple(p.split('=', 1)) for p in opt.params),
Paweł Hajdan, Jr24025d32017-07-11 16:38:21 +0200154 start=opt.start, # Default: None
155 limit=opt.limit, # Default: None
156 o_params=opt.o_params, # Default: None
Michael Achenbach6fbf12f2017-07-06 10:54:11 +0200157 )
158 logging.info('Change query returned %d changes.', len(result))
159 write_result(result, opt)
160
161
LaMont Jones9eed4232021-04-02 16:29:49 +0000162@subcommand.usage('[args ...]')
Marco Georgaklis85557a02021-06-03 15:56:54 +0000163def CMDrelatedchanges(parser, args):
Michael Moss5eebf6f2021-07-16 13:18:34 +0000164 """Gets related changes for a given change and revision."""
Marco Georgaklis85557a02021-06-03 15:56:54 +0000165 parser.add_option('-c', '--change', type=str, help='change id')
166 parser.add_option('-r', '--revision', type=str, help='revision id')
167
168 (opt, args) = parser.parse_args(args)
169
170 result = gerrit_util.GetRelatedChanges(
171 urlparse.urlparse(opt.host).netloc,
172 change=opt.change,
173 revision=opt.revision,
174 )
175 logging.info(result)
176 write_result(result, opt)
177
178
179@subcommand.usage('[args ...]')
LaMont Jones9eed4232021-04-02 16:29:49 +0000180def CMDcreatechange(parser, args):
Michael Moss5eebf6f2021-07-16 13:18:34 +0000181 """Create a new change in gerrit."""
LaMont Jones9eed4232021-04-02 16:29:49 +0000182 parser.add_option('-s', '--subject', help='subject for change')
183 parser.add_option('-b',
184 '--branch',
185 default='main',
186 help='target branch for change')
187 parser.add_option(
188 '-p',
189 '--param',
190 dest='params',
191 action='append',
192 help='repeatable field value parameter, format: -p key=value')
193
194 (opt, args) = parser.parse_args(args)
195 for p in opt.params:
196 assert '=' in p, '--param is key=value, not "%s"' % p
197
198 result = gerrit_util.CreateChange(
199 urlparse.urlparse(opt.host).netloc,
200 opt.project,
201 branch=opt.branch,
202 subject=opt.subject,
203 params=list(tuple(p.split('=', 1)) for p in opt.params),
204 )
205 logging.info(result)
206 write_result(result, opt)
207
208
209@subcommand.usage('[args ...]')
210def CMDchangeedit(parser, args):
Michael Moss5eebf6f2021-07-16 13:18:34 +0000211 """Puts content of a file into a change edit."""
LaMont Jones9eed4232021-04-02 16:29:49 +0000212 parser.add_option('-c', '--change', type=int, help='change number')
213 parser.add_option('--path', help='path for file')
214 parser.add_option('--file', help='file to place at |path|')
215
216 (opt, args) = parser.parse_args(args)
217
218 with open(opt.file) as f:
219 data = f.read()
220 result = gerrit_util.ChangeEdit(
221 urlparse.urlparse(opt.host).netloc, opt.change, opt.path, data)
222 logging.info(result)
223 write_result(result, opt)
224
225
226@subcommand.usage('[args ...]')
227def CMDpublishchangeedit(parser, args):
Michael Moss5eebf6f2021-07-16 13:18:34 +0000228 """Publish a Gerrit change edit."""
LaMont Jones9eed4232021-04-02 16:29:49 +0000229 parser.add_option('-c', '--change', type=int, help='change number')
230 parser.add_option('--notify', help='whether to notify')
231
232 (opt, args) = parser.parse_args(args)
233
234 result = gerrit_util.PublishChangeEdit(
235 urlparse.urlparse(opt.host).netloc, opt.change, opt.notify)
236 logging.info(result)
237 write_result(result, opt)
238
239
Xinan Lin6ec7cd82021-07-21 00:53:42 +0000240@subcommand.usage('[args ...]')
241def CMDsubmitchange(parser, args):
242 """Submit a Gerrit change."""
243 parser.add_option('-c', '--change', type=int, help='change number')
Xinan Lin6ec7cd82021-07-21 00:53:42 +0000244 (opt, args) = parser.parse_args(args)
Xinan Lin1bd4ffa2021-07-28 00:54:22 +0000245 result = gerrit_util.SubmitChange(
246 urlparse.urlparse(opt.host).netloc, opt.change)
Xinan Lin6ec7cd82021-07-21 00:53:42 +0000247 logging.info(result)
248 write_result(result, opt)
249
250
Xinan Linc2fb26a2021-07-27 18:01:55 +0000251@subcommand.usage('[args ...]')
252def CMDgetcommitincludedin(parser, args):
253 """Retrieves the branches and tags for a given commit."""
254 parser.add_option('--commit', dest='commit', help='commit hash')
255 (opt, args) = parser.parse_args(args)
256 result = gerrit_util.GetCommitIncludedIn(
257 urlparse.urlparse(opt.host).netloc, opt.project, opt.commit)
258 logging.info(result)
259 write_result(result, opt)
260
261
Xinan Lin0b0738d2021-07-27 19:13:49 +0000262@subcommand.usage('[args ...]')
263def CMDsetbotcommit(parser, args):
264 """Sets bot-commit+1 to a bot generated change."""
265 parser.add_option('-c', '--change', type=int, help='change number')
266 (opt, args) = parser.parse_args(args)
267 result = gerrit_util.SetReview(
268 urlparse.urlparse(opt.host).netloc,
269 opt.change,
270 labels={'Bot-Commit': 1},
271 ready=True)
272 logging.info(result)
273 write_result(result, opt)
274
275
Sergiy Belozorovfe347232019-02-27 15:07:33 +0000276@subcommand.usage('')
277def CMDabandon(parser, args):
Michael Moss5eebf6f2021-07-16 13:18:34 +0000278 """Abandons a Gerrit change."""
Sergiy Belozorovfe347232019-02-27 15:07:33 +0000279 parser.add_option('-c', '--change', type=int, help='change number')
280 parser.add_option('-m', '--message', default='', help='reason for abandoning')
281
282 (opt, args) = parser.parse_args(args)
Josip Sokcevicc99efb22020-03-17 00:35:34 +0000283 assert opt.change, "-c not defined"
Sergiy Belozorovfe347232019-02-27 15:07:33 +0000284 result = gerrit_util.AbandonChange(
285 urlparse.urlparse(opt.host).netloc,
286 opt.change, opt.message)
287 logging.info(result)
288 write_result(result, opt)
289
290
Michael Moss5eebf6f2021-07-16 13:18:34 +0000291@subcommand.usage('')
Josip Sokcevice1a98942021-04-07 21:35:29 +0000292def CMDmass_abandon(parser, args):
Michael Moss5eebf6f2021-07-16 13:18:34 +0000293 """Mass abandon changes
294
295 Abandons CLs that match search criteria provided by user. Before any change is
296 actually abandoned, user is presented with a list of CLs that will be affected
297 if user confirms. User can skip confirmation by passing --force parameter.
298
299 The script can abandon up to 100 CLs per invocation.
300
301 Examples:
302 gerrit_client.py mass-abandon --host https://HOST -p 'project=repo2'
303 gerrit_client.py mass-abandon --host https://HOST -p 'message=testing'
304 gerrit_client.py mass-abandon --host https://HOST -p 'is=wip' -p 'age=1y'
305 """
Josip Sokcevice1a98942021-04-07 21:35:29 +0000306 parser.add_option('-p',
307 '--param',
308 dest='params',
309 action='append',
310 default=[],
311 help='repeatable query parameter, format: -p key=value')
312 parser.add_option('-m', '--message', default='', help='reason for abandoning')
313 parser.add_option('-f',
314 '--force',
315 action='store_true',
316 help='Don\'t prompt for confirmation')
317
318 opt, args = parser.parse_args(args)
319
320 for p in opt.params:
321 assert '=' in p, '--param is key=value, not "%s"' % p
322 search_query = list(tuple(p.split('=', 1)) for p in opt.params)
323 if not any(t for t in search_query if t[0] == 'owner'):
324 # owner should always be present when abandoning changes
325 search_query.append(('owner', 'me'))
326 search_query.append(('status', 'open'))
327 logging.info("Searching for: %s" % search_query)
328
329 host = urlparse.urlparse(opt.host).netloc
330
331 result = gerrit_util.QueryChanges(
332 host,
333 search_query,
334 # abandon at most 100 changes as not all Gerrit instances support
335 # unlimited results.
336 limit=100,
337 )
338 if len(result) == 0:
339 logging.warn("Nothing to abandon")
340 return
341
342 logging.warn("%s CLs match search query: " % len(result))
343 for change in result:
344 logging.warn("[ID: %d] %s" % (change['_number'], change['subject']))
345
346 if not opt.force:
347 q = raw_input(
348 'Do you want to move forward with abandoning? [y to confirm] ').strip()
349 if q not in ['y', 'Y']:
350 logging.warn("Aborting...")
351 return
352
353 for change in result:
354 logging.warning("Abandoning: %s" % change['subject'])
355 gerrit_util.AbandonChange(host, change['id'], opt.message)
356
357 logging.warning("Done")
358
359
dimu833c94c2017-01-18 17:36:15 -0800360class OptionParser(optparse.OptionParser):
361 """Creates the option parse and add --verbose support."""
362 def __init__(self, *args, **kwargs):
Josip Sokcevicc99efb22020-03-17 00:35:34 +0000363 optparse.OptionParser.__init__(self, *args, version=__version__, **kwargs)
dimu833c94c2017-01-18 17:36:15 -0800364 self.add_option(
365 '--verbose', action='count', default=0,
366 help='Use 2 times for more debugging info')
367 self.add_option('--host', dest='host', help='Url of host.')
368 self.add_option('--project', dest='project', help='project name')
369 self.add_option(
370 '--json_file', dest='json_file', help='output json filepath')
371
372 def parse_args(self, args=None, values=None):
373 options, args = optparse.OptionParser.parse_args(self, args, values)
Josip Sokcevicc99efb22020-03-17 00:35:34 +0000374 # Host is always required
375 assert options.host, "--host not defined."
dimu833c94c2017-01-18 17:36:15 -0800376 levels = [logging.WARNING, logging.INFO, logging.DEBUG]
377 logging.basicConfig(level=levels[min(options.verbose, len(levels) - 1)])
378 return options, args
379
380
381def main(argv):
382 if sys.hexversion < 0x02060000:
383 print('\nYour python version %s is unsupported, please upgrade.\n'
Quinten Yearsleyd9cbe7a2019-09-03 16:49:11 +0000384 % (sys.version.split(' ', 1)[0],),
dimu833c94c2017-01-18 17:36:15 -0800385 file=sys.stderr)
386 return 2
387 dispatcher = subcommand.CommandDispatcher(__name__)
388 return dispatcher.execute(OptionParser(), argv)
389
390
391if __name__ == '__main__':
392 # These affect sys.stdout so do it outside of main() to simplify mocks in
393 # unit testing.
394 fix_encoding.fix_encoding()
395 setup_color.init()
396 try:
397 sys.exit(main(sys.argv[1:]))
398 except KeyboardInterrupt:
399 sys.stderr.write('interrupted\n')
Michael Achenbach6fbf12f2017-07-06 10:54:11 +0200400 sys.exit(1)