blob: ce24178e852d7c1f1a0818ba04fabfc2a69a8fcf [file] [log] [blame]
Alexandru M Stan725c71f2019-12-11 16:53:33 -08001#!/usr/bin/env python3
Brian Norris6baeb2e2020-03-18 12:13:30 -07002# -*- coding: utf-8 -*-
3#
Alexandru M Stanfb5b5ee2014-12-04 13:32:55 -08004# Copyright 2017 The Chromium OS Authors. All rights reserved.
5# Use of this source code is governed by a BSD-style license that can be
6# found in the LICENSE file.
Mike Frysingerf80ca212018-07-13 15:02:52 -04007
Alexandru M Stanfb5b5ee2014-12-04 13:32:55 -08008"""This is a tool for picking patches from upstream and applying them."""
9
Alexandru M Stanfb5b5ee2014-12-04 13:32:55 -080010import argparse
Alexandru M Stan725c71f2019-12-11 16:53:33 -080011import configparser
Tzung-Bi Shih436fdba2019-09-04 19:05:00 +080012import functools
Brian Norrisc3421042018-08-15 14:17:26 -070013import mailbox
Alexandru M Stanfb5b5ee2014-12-04 13:32:55 -080014import os
Tzung-Bi Shih5100c742019-09-02 10:28:32 +080015import pprint
Alexandru M Stanfb5b5ee2014-12-04 13:32:55 -080016import re
17import signal
18import subprocess
19import sys
Harry Cuttsae372f32019-02-12 18:01:14 -080020import textwrap
Alexandru M Stan725c71f2019-12-11 16:53:33 -080021import urllib.request
22import xmlrpc.client
Alexandru M Stanfb5b5ee2014-12-04 13:32:55 -080023
Tzung-Bi Shih436fdba2019-09-04 19:05:00 +080024errprint = functools.partial(print, file=sys.stderr)
25
Abhishek Pandit-Subediaea8c502020-07-09 21:56:12 -070026UPSTREAM_URLS = (
Alexandru M Stanfb5b5ee2014-12-04 13:32:55 -080027 'git://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git',
28 'https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git',
29 'https://kernel.googlesource.com/pub/scm/linux/kernel/git/torvalds/linux.git',
Brian Norris8043cfd2020-03-19 11:46:16 -070030 'git://w1.fi/srv/git/hostap.git',
Abhishek Pandit-Subediaea8c502020-07-09 21:56:12 -070031 'git://git.kernel.org/pub/scm/bluetooth/bluez.git',
Brian Norris8043cfd2020-03-19 11:46:16 -070032)
33
Stephen Boydb68c17a2019-09-26 15:08:02 -070034PATCHWORK_URLS = (
35 'https://lore.kernel.org/patchwork',
36 'https://patchwork.kernel.org',
37 'https://patchwork.ozlabs.org',
38 'https://patchwork.freedesktop.org',
39 'https://patchwork.linux-mips.org',
40)
41
Harry Cuttsae372f32019-02-12 18:01:14 -080042COMMIT_MESSAGE_WIDTH = 75
43
Brian Norris9f8a2be2018-06-01 11:14:08 -070044_PWCLIENTRC = os.path.expanduser('~/.pwclientrc')
45
Alexandru M Stan725c71f2019-12-11 16:53:33 -080046def _git(args, stdin=None, encoding='utf-8'):
47 """Calls a git subcommand.
48
49 Similar to subprocess.check_output.
50
51 Args:
Brian Norris6baeb2e2020-03-18 12:13:30 -070052 args: subcommand + args passed to 'git'.
Alexandru M Stan725c71f2019-12-11 16:53:33 -080053 stdin: a string or bytes (depending on encoding) that will be passed
54 to the git subcommand.
55 encoding: either 'utf-8' (default) or None. Override it to None if
56 you want both stdin and stdout to be raw bytes.
57
58 Returns:
59 the stdout of the git subcommand, same type as stdin. The output is
60 also run through strip to make sure there's no extra whitespace.
61
62 Raises:
63 subprocess.CalledProcessError: when return code is not zero.
64 The exception has a .returncode attribute.
65 """
66 return subprocess.run(
67 ['git'] + args,
68 encoding=encoding,
69 input=stdin,
70 stdout=subprocess.PIPE,
71 check=True,
72 ).stdout.strip()
73
74def _git_returncode(*args, **kwargs):
75 """Same as _git, but return returncode instead of stdout.
76
77 Similar to subprocess.call.
78
79 Never raises subprocess.CalledProcessError.
80 """
81 try:
82 _git(*args, **kwargs)
83 return 0
84 except subprocess.CalledProcessError as e:
85 return e.returncode
86
Guenter Roeckbdbb9cc2018-04-19 10:05:08 -070087def _get_conflicts():
88 """Report conflicting files."""
89 resolutions = ('DD', 'AU', 'UD', 'UA', 'DU', 'AA', 'UU')
90 conflicts = []
Alexandru M Stan725c71f2019-12-11 16:53:33 -080091 output = _git(['status', '--porcelain', '--untracked-files=no'])
92 for line in output.splitlines():
Douglas Anderson46287f92018-04-30 09:58:24 -070093 if not line:
Guenter Roeckbdbb9cc2018-04-19 10:05:08 -070094 continue
Douglas Anderson46287f92018-04-30 09:58:24 -070095 resolution, name = line.split(None, 1)
Guenter Roeckbdbb9cc2018-04-19 10:05:08 -070096 if resolution in resolutions:
97 conflicts.append(' ' + name)
98 if not conflicts:
Douglas Andersonb6a10fe2019-08-12 13:53:30 -070099 return ''
Guenter Roeckbdbb9cc2018-04-19 10:05:08 -0700100 return '\nConflicts:\n%s\n' % '\n'.join(conflicts)
101
Brian Norris8043cfd2020-03-19 11:46:16 -0700102def _find_upstream_remote(urls):
103 """Find a remote pointing to an upstream repository."""
Alexandru M Stan725c71f2019-12-11 16:53:33 -0800104 for remote in _git(['remote']).splitlines():
105 try:
Brian Norris8043cfd2020-03-19 11:46:16 -0700106 if _git(['remote', 'get-url', remote]) in urls:
Alexandru M Stan725c71f2019-12-11 16:53:33 -0800107 return remote
108 except subprocess.CalledProcessError:
109 # Kinda weird, get-url failing on an item that git just gave us.
110 continue
Guenter Roeckd66daa72018-04-19 10:31:25 -0700111 return None
112
Guenter Roeckbdbb9cc2018-04-19 10:05:08 -0700113def _pause_for_merge(conflicts):
Alexandru M Stanfb5b5ee2014-12-04 13:32:55 -0800114 """Pause and go in the background till user resolves the conflicts."""
115
Alexandru M Stan725c71f2019-12-11 16:53:33 -0800116 git_root = _git(['rev-parse', '--show-toplevel'])
Harry Cutts2bcd9af2020-02-20 16:27:50 -0800117 previous_head_hash = _git(['rev-parse', 'HEAD'])
Alexandru M Stanfb5b5ee2014-12-04 13:32:55 -0800118
119 paths = (
120 os.path.join(git_root, '.git', 'rebase-apply'),
121 os.path.join(git_root, '.git', 'CHERRY_PICK_HEAD'),
122 )
123 for path in paths:
124 if os.path.exists(path):
Tzung-Bi Shih436fdba2019-09-04 19:05:00 +0800125 errprint('Found "%s".' % path)
126 errprint(conflicts)
127 errprint('Please resolve the conflicts and restart the '
128 'shell job when done. Kill this job if you '
129 'aborted the conflict.')
Alexandru M Stanfb5b5ee2014-12-04 13:32:55 -0800130 os.kill(os.getpid(), signal.SIGTSTP)
Harry Cutts2bcd9af2020-02-20 16:27:50 -0800131
132 # Check the conflicts actually got resolved. Otherwise we'll end up
133 # modifying the wrong commit message and probably confusing people.
134 while previous_head_hash == _git(['rev-parse', 'HEAD']):
135 errprint('Error: no new commit has been made. Did you forget to run '
136 '`git am --continue` or `git cherry-pick --continue`?')
137 errprint('Please create a new commit and restart the shell job (or kill'
138 ' it if you aborted the conflict).')
139 os.kill(os.getpid(), signal.SIGTSTP)
Alexandru M Stanfb5b5ee2014-12-04 13:32:55 -0800140
Brian Norris9f8a2be2018-06-01 11:14:08 -0700141def _get_pw_url(project):
142 """Retrieve the patchwork server URL from .pwclientrc.
143
Mike Frysingerf80ca212018-07-13 15:02:52 -0400144 Args:
145 project: patchwork project name; if None, we retrieve the default
146 from pwclientrc
Brian Norris9f8a2be2018-06-01 11:14:08 -0700147 """
Alexandru M Stan725c71f2019-12-11 16:53:33 -0800148 config = configparser.ConfigParser()
Brian Norris9f8a2be2018-06-01 11:14:08 -0700149 config.read([_PWCLIENTRC])
150
151 if project is None:
152 try:
153 project = config.get('options', 'default')
Alexandru M Stan725c71f2019-12-11 16:53:33 -0800154 except (configparser.NoSectionError, configparser.NoOptionError) as e:
155 errprint('Error: no default patchwork project found in %s. (%r)'
156 % (_PWCLIENTRC, e))
Brian Norris9f8a2be2018-06-01 11:14:08 -0700157 sys.exit(1)
158
159 if not config.has_option(project, 'url'):
Tzung-Bi Shih436fdba2019-09-04 19:05:00 +0800160 errprint("Error: patchwork URL not found for project '%s'" % project)
Brian Norris9f8a2be2018-06-01 11:14:08 -0700161 sys.exit(1)
162
163 url = config.get(project, 'url')
Brian Norris2d4e9762018-08-15 13:11:47 -0700164 # Strip trailing 'xmlrpc' and/or trailing slash.
165 return re.sub('/(xmlrpc/)?$', '', url)
Brian Norris9f8a2be2018-06-01 11:14:08 -0700166
Harry Cuttsae372f32019-02-12 18:01:14 -0800167def _wrap_commit_line(prefix, content):
168 line = prefix + '=' + content
169 indent = ' ' * (len(prefix) + 1)
Tzung-Bi Shihc4cfabb2020-08-10 12:57:23 +0800170
171 ret = textwrap.fill(line, COMMIT_MESSAGE_WIDTH, subsequent_indent=indent)
172 return ret[len(prefix) + 1:]
Harry Cuttsae372f32019-02-12 18:01:14 -0800173
Stephen Boydb68c17a2019-09-26 15:08:02 -0700174def _pick_patchwork(url, patch_id, args):
Tzung-Bi Shih886c9092019-09-02 12:46:16 +0800175 if args['tag'] is None:
176 args['tag'] = 'FROMLIST: '
177
Brian Norris8553f032020-03-18 11:59:02 -0700178 try:
179 opener = urllib.request.urlopen('%s/patch/%d/mbox' % (url, patch_id))
180 except urllib.error.HTTPError as e:
181 errprint('Error: could not download patch: %s' % e)
Tzung-Bi Shih886c9092019-09-02 12:46:16 +0800182 sys.exit(1)
183 patch_contents = opener.read()
184
185 if not patch_contents:
Tzung-Bi Shih436fdba2019-09-04 19:05:00 +0800186 errprint('Error: No patch content found')
Tzung-Bi Shih886c9092019-09-02 12:46:16 +0800187 sys.exit(1)
188
Douglas Andersonbecd4e62019-09-25 13:40:55 -0700189 message_id = mailbox.Message(patch_contents)['Message-Id']
190 message_id = re.sub('^<|>$', '', message_id.strip())
Tzung-Bi Shih886c9092019-09-02 12:46:16 +0800191 if args['source_line'] is None:
192 args['source_line'] = '(am from %s/patch/%d/)' % (url, patch_id)
Brian Norris8553f032020-03-18 11:59:02 -0700193 for url_template in [
Brian Norris655b5ce2020-05-08 11:37:38 -0700194 'https://lore.kernel.org/r/%s',
Brian Norris8553f032020-03-18 11:59:02 -0700195 # hostap project (and others) are here, but not kernel.org.
196 'https://marc.info/?i=%s',
197 # public-inbox comes last as a "default"; it has a nice error page
198 # pointing to other redirectors, even if it doesn't have what
199 # you're looking for directly.
200 'https://public-inbox.org/git/%s',
201 ]:
202 alt_url = url_template % message_id
203 if args['debug']:
204 print('Probing archive for message at: %s' % alt_url)
205 try:
206 urllib.request.urlopen(alt_url)
207 except urllib.error.HTTPError as e:
208 # Skip all HTTP errors. We can expect 404 for archives that
209 # don't have this MessageId, or 300 for public-inbox ("not
210 # found, but try these other redirects"). It's less clear what
211 # to do with transitory (or is it permanent?) server failures.
212 if args['debug']:
213 print('Skipping URL %s, error: %s' % (alt_url, e))
214 continue
215 # Success!
216 if args['debug']:
217 print('Found at %s' % alt_url)
218 break
219 else:
220 errprint(
221 "WARNING: couldn't find working MessageId URL; "
222 'defaulting to "%s"' % alt_url)
223 args['source_line'] += '\n(also found at %s)' % alt_url
Tzung-Bi Shih886c9092019-09-02 12:46:16 +0800224
Douglas Andersonbecd4e62019-09-25 13:40:55 -0700225 # Auto-snarf the Change-Id if it was encoded into the Message-Id.
226 mo = re.match(r'.*(I[a-f0-9]{40})@changeid$', message_id)
227 if mo and args['changeid'] is None:
228 args['changeid'] = mo.group(1)
229
Tzung-Bi Shih886c9092019-09-02 12:46:16 +0800230 if args['replace']:
Alexandru M Stan725c71f2019-12-11 16:53:33 -0800231 _git(['reset', '--hard', 'HEAD~1'])
Tzung-Bi Shih886c9092019-09-02 12:46:16 +0800232
Tzung-Bi Shih3a4ebda2020-08-10 09:45:19 +0800233 return _git_returncode(['am', '-3', '--reject'], stdin=patch_contents,
234 encoding=None)
Tzung-Bi Shih886c9092019-09-02 12:46:16 +0800235
Stephen Boydb68c17a2019-09-26 15:08:02 -0700236def _match_patchwork(match, args):
237 """Match location: pw://### or pw://PROJECT/###."""
238 pw_project = match.group(2)
239 patch_id = int(match.group(3))
240
241 if args['debug']:
242 print('_match_patchwork: pw_project=%s, patch_id=%d' %
243 (pw_project, patch_id))
244
245 url = _get_pw_url(pw_project)
246 return _pick_patchwork(url, patch_id, args)
247
248def _match_msgid(match, args):
249 """Match location: msgid://MSGID."""
250 msgid = match.group(1)
251
252 if args['debug']:
253 print('_match_msgid: message_id=%s' % (msgid))
254
255 # Patchwork requires the brackets so force it
256 msgid = '<' + msgid + '>'
257 url = None
258 for url in PATCHWORK_URLS:
Alexandru M Stan725c71f2019-12-11 16:53:33 -0800259 rpc = xmlrpc.client.ServerProxy(url + '/xmlrpc/')
Stephen Boydb68c17a2019-09-26 15:08:02 -0700260 res = rpc.patch_list({'msgid': msgid})
261 if res:
262 patch_id = res[0]['id']
263 break
264 else:
265 errprint('Error: could not find patch based on message id')
266 sys.exit(1)
267
268 return _pick_patchwork(url, patch_id, args)
269
Abhishek Pandit-Subediaea8c502020-07-09 21:56:12 -0700270def _upstream(commit, urls, args):
Tzung-Bi Shih886c9092019-09-02 12:46:16 +0800271 if args['debug']:
Abhishek Pandit-Subediaea8c502020-07-09 21:56:12 -0700272 print('_upstream: commit=%s' % commit)
Tzung-Bi Shih886c9092019-09-02 12:46:16 +0800273
Brian Norris8043cfd2020-03-19 11:46:16 -0700274 # Confirm an upstream remote is setup.
275 remote = _find_upstream_remote(urls)
276 if not remote:
Tzung-Bi Shih436fdba2019-09-04 19:05:00 +0800277 errprint('Error: need a valid upstream remote')
Tzung-Bi Shih886c9092019-09-02 12:46:16 +0800278 sys.exit(1)
279
Brian Norris8043cfd2020-03-19 11:46:16 -0700280 remote_ref = '%s/master' % remote
Alexandru M Stan725c71f2019-12-11 16:53:33 -0800281 try:
Brian Norris8043cfd2020-03-19 11:46:16 -0700282 _git(['merge-base', '--is-ancestor', commit, remote_ref])
Alexandru M Stan725c71f2019-12-11 16:53:33 -0800283 except subprocess.CalledProcessError:
Brian Norris8043cfd2020-03-19 11:46:16 -0700284 errprint('Error: Commit not in %s' % remote_ref)
Tzung-Bi Shih886c9092019-09-02 12:46:16 +0800285 sys.exit(1)
286
287 if args['source_line'] is None:
Alexandru M Stan725c71f2019-12-11 16:53:33 -0800288 commit = _git(['rev-parse', commit])
Tzung-Bi Shih886c9092019-09-02 12:46:16 +0800289 args['source_line'] = ('(cherry picked from commit %s)' %
290 (commit))
291 if args['tag'] is None:
292 args['tag'] = 'UPSTREAM: '
293
294 if args['replace']:
Alexandru M Stan725c71f2019-12-11 16:53:33 -0800295 _git(['reset', '--hard', 'HEAD~1'])
Tzung-Bi Shih886c9092019-09-02 12:46:16 +0800296
Alexandru M Stan725c71f2019-12-11 16:53:33 -0800297 return _git_returncode(['cherry-pick', commit])
Tzung-Bi Shih886c9092019-09-02 12:46:16 +0800298
Abhishek Pandit-Subediaea8c502020-07-09 21:56:12 -0700299def _match_upstream(match, args):
300 """Match location: linux://HASH and upstream://HASH."""
Brian Norris8043cfd2020-03-19 11:46:16 -0700301 commit = match.group(1)
Abhishek Pandit-Subediaea8c502020-07-09 21:56:12 -0700302 return _upstream(commit, urls=UPSTREAM_URLS, args=args)
Brian Norris8043cfd2020-03-19 11:46:16 -0700303
Tzung-Bi Shih886c9092019-09-02 12:46:16 +0800304def _match_fromgit(match, args):
305 """Match location: git://remote/branch/HASH."""
306 remote = match.group(2)
307 branch = match.group(3)
308 commit = match.group(4)
309
310 if args['debug']:
311 print('_match_fromgit: remote=%s branch=%s commit=%s' %
312 (remote, branch, commit))
313
Alexandru M Stan725c71f2019-12-11 16:53:33 -0800314 try:
315 _git(['merge-base', '--is-ancestor', commit,
316 '%s/%s' % (remote, branch)])
317 except subprocess.CalledProcessError:
Tzung-Bi Shih436fdba2019-09-04 19:05:00 +0800318 errprint('Error: Commit not in %s/%s' % (remote, branch))
Tzung-Bi Shih886c9092019-09-02 12:46:16 +0800319 sys.exit(1)
320
Alexandru M Stan725c71f2019-12-11 16:53:33 -0800321 url = _git(['remote', 'get-url', remote])
Tzung-Bi Shih886c9092019-09-02 12:46:16 +0800322
323 if args['source_line'] is None:
Alexandru M Stan725c71f2019-12-11 16:53:33 -0800324 commit = _git(['rev-parse', commit])
Tzung-Bi Shiha8f310d2019-09-04 19:10:10 +0800325 args['source_line'] = (
326 '(cherry picked from commit %s\n %s %s)' % (commit, url, branch))
Tzung-Bi Shih886c9092019-09-02 12:46:16 +0800327 if args['tag'] is None:
328 args['tag'] = 'FROMGIT: '
329
330 if args['replace']:
Alexandru M Stan725c71f2019-12-11 16:53:33 -0800331 _git(['reset', '--hard', 'HEAD~1'])
Tzung-Bi Shih886c9092019-09-02 12:46:16 +0800332
Alexandru M Stan725c71f2019-12-11 16:53:33 -0800333 return _git_returncode(['cherry-pick', commit])
Tzung-Bi Shih886c9092019-09-02 12:46:16 +0800334
335def _match_gitfetch(match, args):
336 """Match location: (git|https)://repoURL#branch/HASH."""
337 remote = match.group(1)
338 branch = match.group(3)
339 commit = match.group(4)
340
341 if args['debug']:
342 print('_match_gitfetch: remote=%s branch=%s commit=%s' %
343 (remote, branch, commit))
344
Alexandru M Stan725c71f2019-12-11 16:53:33 -0800345 try:
346 _git(['fetch', remote, branch])
347 except subprocess.CalledProcessError:
Tzung-Bi Shih436fdba2019-09-04 19:05:00 +0800348 errprint('Error: Branch not in %s' % remote)
Tzung-Bi Shih886c9092019-09-02 12:46:16 +0800349 sys.exit(1)
350
351 url = remote
352
353 if args['source_line'] is None:
Alexandru M Stan725c71f2019-12-11 16:53:33 -0800354 commit = _git(['rev-parse', commit])
Tzung-Bi Shiha8f310d2019-09-04 19:10:10 +0800355 args['source_line'] = (
356 '(cherry picked from commit %s\n %s %s)' % (commit, url, branch))
Tzung-Bi Shih886c9092019-09-02 12:46:16 +0800357 if args['tag'] is None:
358 args['tag'] = 'FROMGIT: '
359
Stephen Boyd4b3869a2020-01-24 15:35:37 -0800360 if args['replace']:
361 _git(['reset', '--hard', 'HEAD~1'])
362
Alexandru M Stan725c71f2019-12-11 16:53:33 -0800363 return _git_returncode(['cherry-pick', commit])
Tzung-Bi Shih886c9092019-09-02 12:46:16 +0800364
Stephen Boyd96396032020-02-25 10:12:59 -0800365def _match_gitweb(match, args):
366 """Match location: https://repoURL/commit/?h=branch&id=HASH."""
367 remote = match.group(1)
368 branch = match.group(2)
369 commit = match.group(3)
370
371 if args['debug']:
372 print('_match_gitweb: remote=%s branch=%s commit=%s' %
373 (remote, branch, commit))
374
375 try:
376 _git(['fetch', remote, branch])
377 except subprocess.CalledProcessError:
378 errprint('Error: Branch not in %s' % remote)
379 sys.exit(1)
380
381 url = remote
382
383 if args['source_line'] is None:
384 commit = _git(['rev-parse', commit])
385 args['source_line'] = (
386 '(cherry picked from commit %s\n %s %s)' % (commit, url, branch))
387 if args['tag'] is None:
388 args['tag'] = 'FROMGIT: '
389
390 if args['replace']:
391 _git(['reset', '--hard', 'HEAD~1'])
392
393 return _git_returncode(['cherry-pick', commit])
394
Alexandru M Stanfb5b5ee2014-12-04 13:32:55 -0800395def main(args):
396 """This is the main entrypoint for fromupstream.
397
398 Args:
399 args: sys.argv[1:]
400
401 Returns:
402 An int return code.
403 """
404 parser = argparse.ArgumentParser()
405
406 parser.add_argument('--bug', '-b',
Guenter Roeckf47a50c2018-07-25 12:41:36 -0700407 type=str, help='BUG= line')
Alexandru M Stanfb5b5ee2014-12-04 13:32:55 -0800408 parser.add_argument('--test', '-t',
Guenter Roeckf47a50c2018-07-25 12:41:36 -0700409 type=str, help='TEST= line')
Stephen Boyd24b309b2018-11-06 22:11:00 -0800410 parser.add_argument('--crbug', action='append',
411 type=int, help='BUG=chromium: line')
412 parser.add_argument('--buganizer', action='append',
413 type=int, help='BUG=b: line')
Alexandru M Stanfb5b5ee2014-12-04 13:32:55 -0800414 parser.add_argument('--changeid', '-c',
415 help='Overrides the gerrit generated Change-Id line')
Tzung-Bi Shih1a262c02020-08-13 10:49:55 +0800416 parser.add_argument('--cqdepend',
417 type=str, help='Cq-Depend: line')
Alexandru M Stanfb5b5ee2014-12-04 13:32:55 -0800418
Tzung-Bi Shihf5d25a82019-09-02 11:40:09 +0800419 parser.add_argument('--replace', '-r',
Alexandru M Stanfb5b5ee2014-12-04 13:32:55 -0800420 action='store_true',
Tzung-Bi Shih196d31e2019-09-01 18:16:28 +0800421 help='Replaces the HEAD commit with this one, taking '
422 'its properties(BUG, TEST, Change-Id). Useful for '
Alexandru M Stanfb5b5ee2014-12-04 13:32:55 -0800423 'updating commits.')
424 parser.add_argument('--nosignoff',
425 dest='signoff', action='store_false')
Tzung-Bi Shih5100c742019-09-02 10:28:32 +0800426 parser.add_argument('--debug', '-d', action='store_true',
427 help='Prints more verbose logs.')
Alexandru M Stanfb5b5ee2014-12-04 13:32:55 -0800428
429 parser.add_argument('--tag',
430 help='Overrides the tag from the title')
431 parser.add_argument('--source', '-s',
432 dest='source_line', type=str,
Tzung-Bi Shih196d31e2019-09-01 18:16:28 +0800433 help='Overrides the source line, last line, ex: '
Alexandru M Stanfb5b5ee2014-12-04 13:32:55 -0800434 '(am from http://....)')
435 parser.add_argument('locations',
Douglas Andersonc77a8b82018-05-04 17:02:03 -0700436 nargs='+',
Tzung-Bi Shih196d31e2019-09-01 18:16:28 +0800437 help='Patchwork ID (pw://### or pw://PROJECT/###, '
438 'where PROJECT is defined in ~/.pwclientrc; if no '
439 'PROJECT is specified, the default is retrieved from '
440 '~/.pwclientrc), '
Stephen Boydb68c17a2019-09-26 15:08:02 -0700441 'Message-ID (msgid://MSGID), '
Brian Norris8043cfd2020-03-19 11:46:16 -0700442 'linux commit like linux://HASH, '
Abhishek Pandit-Subediaea8c502020-07-09 21:56:12 -0700443 'upstream commit like upstream://HASH, or '
Tzung-Bi Shih196d31e2019-09-01 18:16:28 +0800444 'git reference like git://remote/branch/HASH or '
445 'git://repoURL#branch/HASH or '
Stephen Boyd96396032020-02-25 10:12:59 -0800446 'https://repoURL#branch/HASH or '
447 'https://repoURL/commit/?h=branch&id=HASH')
Alexandru M Stanfb5b5ee2014-12-04 13:32:55 -0800448
449 args = vars(parser.parse_args(args))
450
Stephen Boyd24b309b2018-11-06 22:11:00 -0800451 buglist = [args['bug']] if args['bug'] else []
452 if args['buganizer']:
453 buglist += ['b:{0}'.format(x) for x in args['buganizer']]
454 if args['crbug']:
455 buglist += ['chromium:{0}'.format(x) for x in args['crbug']]
Brian Norris667a0cb2018-12-07 09:28:46 -0800456 if buglist:
457 args['bug'] = ', '.join(buglist)
Stephen Boyd24b309b2018-11-06 22:11:00 -0800458
Tzung-Bi Shihc4cfabb2020-08-10 12:57:23 +0800459 if args['test']:
460 args['test'] = _wrap_commit_line('TEST', args['test'])
461
Alexandru M Stanfb5b5ee2014-12-04 13:32:55 -0800462 if args['replace']:
Alexandru M Stan725c71f2019-12-11 16:53:33 -0800463 old_commit_message = _git(['show', '-s', '--format=%B', 'HEAD'])
Tzung-Bi Shih231fada2019-09-02 00:54:59 +0800464
465 # It is possible that multiple Change-Ids are in the commit message
466 # (due to cherry picking). We only want to pull out the first one.
467 changeid_match = re.search('^Change-Id: (.*)$',
468 old_commit_message, re.MULTILINE)
Tzung-Bi Shih5fd0fd52020-08-13 11:06:33 +0800469 if args['changeid'] is None and changeid_match:
Tzung-Bi Shih231fada2019-09-02 00:54:59 +0800470 args['changeid'] = changeid_match.group(1)
471
Tzung-Bi Shihdfe82002020-08-13 11:00:56 +0800472 cq_depends = re.findall(r'^Cq-Depend:\s+(.*)$',
473 old_commit_message, re.MULTILINE)
474 if args['cqdepend'] is None and cq_depends:
475 args['cqdepend'] = '\nCq-Depend: '.join(cq_depends)
476
Tzung-Bi Shihe1e0e7d2019-09-02 15:04:38 +0800477 bugs = re.findall('^BUG=(.*)$', old_commit_message, re.MULTILINE)
Tzung-Bi Shih04345302019-09-02 12:04:01 +0800478 if args['bug'] is None and bugs:
479 args['bug'] = '\nBUG='.join(bugs)
480
Tzung-Bi Shih095c7522020-08-10 11:32:06 +0800481 # Note: use (?=...) to avoid to consume the source string
482 tests = re.findall(r"""
483 ^TEST=(.*?) # Match start from TEST= until
484 \n # (to remove the tailing newlines)
485 (?=^$| # a blank line
486 ^Cq-Depend:| # or Cq-Depend:
487 ^Change-Id:| # or Change-Id:
488 ^BUG=| # or following BUG=
489 ^TEST=) # or another TEST=
490 """,
491 old_commit_message, re.MULTILINE | re.DOTALL | re.VERBOSE)
Tzung-Bi Shih04345302019-09-02 12:04:01 +0800492 if args['test'] is None and tests:
493 args['test'] = '\nTEST='.join(tests)
Alexandru M Stanfb5b5ee2014-12-04 13:32:55 -0800494
Guenter Roeckf47a50c2018-07-25 12:41:36 -0700495 if args['bug'] is None or args['test'] is None:
Tzung-Bi Shih196d31e2019-09-01 18:16:28 +0800496 parser.error('BUG=/TEST= lines are required; --replace can help '
Stephen Boyde6fdf912018-11-09 10:30:57 -0800497 'automate, or set via --bug/--test')
Guenter Roeckf47a50c2018-07-25 12:41:36 -0700498
Tzung-Bi Shih5100c742019-09-02 10:28:32 +0800499 if args['debug']:
500 pprint.pprint(args)
501
Tzung-Bi Shih886c9092019-09-02 12:46:16 +0800502 re_matches = (
Tzung-Bi Shihe1e0e7d2019-09-02 15:04:38 +0800503 (re.compile(r'^pw://(([^/]+)/)?(\d+)'), _match_patchwork),
Stephen Boydb68c17a2019-09-26 15:08:02 -0700504 (re.compile(r'^msgid://<?([^>]*)>?'), _match_msgid),
Abhishek Pandit-Subediaea8c502020-07-09 21:56:12 -0700505 (re.compile(r'^linux://([0-9a-f]+)'), _match_upstream),
506 (re.compile(r'^upstream://([0-9a-f]+)'), _match_upstream),
Tzung-Bi Shihe1e0e7d2019-09-02 15:04:38 +0800507 (re.compile(r'^(from)?git://([^/\#]+)/([^#]+)/([0-9a-f]+)$'),
Tzung-Bi Shih886c9092019-09-02 12:46:16 +0800508 _match_fromgit),
Tzung-Bi Shihe1e0e7d2019-09-02 15:04:38 +0800509 (re.compile(r'^((git|https)://.+)#(.+)/([0-9a-f]+)$'), _match_gitfetch),
Stephen Boyd96396032020-02-25 10:12:59 -0800510 (re.compile(r'^(https://.+)/commit/\?h=(.+)\&id=([0-9a-f]+)$'), _match_gitweb),
Tzung-Bi Shih886c9092019-09-02 12:46:16 +0800511 )
Alexandru M Stanfb5b5ee2014-12-04 13:32:55 -0800512
Tzung-Bi Shih886c9092019-09-02 12:46:16 +0800513 for location in args['locations']:
Tzung-Bi Shih5100c742019-09-02 10:28:32 +0800514 if args['debug']:
515 print('location=%s' % location)
516
Tzung-Bi Shih886c9092019-09-02 12:46:16 +0800517 for reg, handler in re_matches:
518 match = reg.match(location)
519 if match:
520 ret = handler(match, args)
521 break
Alexandru M Stanfb5b5ee2014-12-04 13:32:55 -0800522 else:
Tzung-Bi Shih436fdba2019-09-04 19:05:00 +0800523 errprint('Don\'t know what "%s" means.' % location)
Alexandru M Stanfb5b5ee2014-12-04 13:32:55 -0800524 sys.exit(1)
525
526 if ret != 0:
Guenter Roeckbdbb9cc2018-04-19 10:05:08 -0700527 conflicts = _get_conflicts()
Douglas Anderson2108e532018-04-30 09:50:42 -0700528 if args['tag'] == 'UPSTREAM: ':
529 args['tag'] = 'BACKPORT: '
530 else:
531 args['tag'] = 'BACKPORT: ' + args['tag']
Guenter Roeckbdbb9cc2018-04-19 10:05:08 -0700532 _pause_for_merge(conflicts)
533 else:
Douglas Andersonb6a10fe2019-08-12 13:53:30 -0700534 conflicts = ''
Alexandru M Stanfb5b5ee2014-12-04 13:32:55 -0800535
536 # extract commit message
Alexandru M Stan725c71f2019-12-11 16:53:33 -0800537 commit_message = _git(['show', '-s', '--format=%B', 'HEAD'])
Alexandru M Stanfb5b5ee2014-12-04 13:32:55 -0800538
Guenter Roeck2e4f2512018-04-24 09:20:51 -0700539 # Remove stray Change-Id, most likely from merge resolution
540 commit_message = re.sub(r'Change-Id:.*\n?', '', commit_message)
541
Brian Norris7a41b982018-06-01 10:28:29 -0700542 # Note the source location before tagging anything else
543 commit_message += '\n' + args['source_line']
544
Alexandru M Stanfb5b5ee2014-12-04 13:32:55 -0800545 # add automatic Change ID, BUG, and TEST (and maybe signoff too) so
546 # next commands know where to work on
547 commit_message += '\n'
Guenter Roeckbdbb9cc2018-04-19 10:05:08 -0700548 commit_message += conflicts
Alexandru M Stanfb5b5ee2014-12-04 13:32:55 -0800549 commit_message += '\n' + 'BUG=' + args['bug']
Tzung-Bi Shihc4cfabb2020-08-10 12:57:23 +0800550 commit_message += '\n' + 'TEST=' + args['test']
Brian Norris674209e2020-04-22 15:33:53 -0700551
552 extra = []
Alexandru M Stanfb5b5ee2014-12-04 13:32:55 -0800553 if args['signoff']:
Brian Norris674209e2020-04-22 15:33:53 -0700554 signoff = 'Signed-off-by: %s <%s>' % (
555 _git(['config', 'user.name']),
556 _git(['config', 'user.email']))
557 if not signoff in commit_message.splitlines():
558 extra += ['-s']
Alexandru M Stan725c71f2019-12-11 16:53:33 -0800559 _git(['commit'] + extra + ['--amend', '-F', '-'], stdin=commit_message)
Alexandru M Stanfb5b5ee2014-12-04 13:32:55 -0800560
561 # re-extract commit message
Alexandru M Stan725c71f2019-12-11 16:53:33 -0800562 commit_message = _git(['show', '-s', '--format=%B', 'HEAD'])
Alexandru M Stanfb5b5ee2014-12-04 13:32:55 -0800563
Douglas Andersonbecd4e62019-09-25 13:40:55 -0700564 # If we see a "Link: " that seems to point to a Message-Id with an
565 # automatic Change-Id we'll snarf it out.
566 mo = re.search(r'^Link:.*(I[a-f0-9]{40})@changeid', commit_message,
567 re.MULTILINE)
568 if mo and args['changeid'] is None:
569 args['changeid'] = mo.group(1)
570
Alexandru M Stanfb5b5ee2014-12-04 13:32:55 -0800571 # replace changeid if needed
572 if args['changeid'] is not None:
573 commit_message = re.sub(r'(Change-Id: )(\w+)', r'\1%s' %
574 args['changeid'], commit_message)
575 args['changeid'] = None
576
Tzung-Bi Shih1a262c02020-08-13 10:49:55 +0800577 if args['cqdepend'] is not None:
578 commit_message = re.sub(
579 r'(Change-Id: \w+)', r'Cq-Depend: %s\n\1' % args['cqdepend'],
580 commit_message)
581
Alexandru M Stanfb5b5ee2014-12-04 13:32:55 -0800582 # decorate it that it's from outside
583 commit_message = args['tag'] + commit_message
Alexandru M Stanfb5b5ee2014-12-04 13:32:55 -0800584
585 # commit everything
Alexandru M Stan725c71f2019-12-11 16:53:33 -0800586 _git(['commit', '--amend', '-F', '-'], stdin=commit_message)
Alexandru M Stanfb5b5ee2014-12-04 13:32:55 -0800587
Chirantan Ekbote4b08e712019-06-12 15:35:41 +0900588 return 0
Alexandru M Stanfb5b5ee2014-12-04 13:32:55 -0800589
590if __name__ == '__main__':
591 sys.exit(main(sys.argv[1:]))