blob: f235d24b5405154955e9d440178a477f96b1ab5f [file] [log] [blame]
Alexandru M Stan725c71f2019-12-11 16:53:33 -08001#!/usr/bin/env python3
Alexandru M Stanfb5b5ee2014-12-04 13:32:55 -08002# Copyright 2017 The Chromium OS 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.
Mike Frysingerf80ca212018-07-13 15:02:52 -04005
Alexandru M Stanfb5b5ee2014-12-04 13:32:55 -08006"""This is a tool for picking patches from upstream and applying them."""
7
Alexandru M Stanfb5b5ee2014-12-04 13:32:55 -08008import argparse
Alexandru M Stan725c71f2019-12-11 16:53:33 -08009import configparser
Tzung-Bi Shih436fdba2019-09-04 19:05:00 +080010import functools
Brian Norrisc3421042018-08-15 14:17:26 -070011import mailbox
Alexandru M Stanfb5b5ee2014-12-04 13:32:55 -080012import os
Tzung-Bi Shih5100c742019-09-02 10:28:32 +080013import pprint
Alexandru M Stanfb5b5ee2014-12-04 13:32:55 -080014import re
15import signal
16import subprocess
17import sys
Harry Cuttsae372f32019-02-12 18:01:14 -080018import textwrap
Alexandru M Stan725c71f2019-12-11 16:53:33 -080019import urllib.request
20import xmlrpc.client
Alexandru M Stanfb5b5ee2014-12-04 13:32:55 -080021
Tzung-Bi Shih436fdba2019-09-04 19:05:00 +080022errprint = functools.partial(print, file=sys.stderr)
23
Alexandru M Stanfb5b5ee2014-12-04 13:32:55 -080024LINUX_URLS = (
25 'git://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git',
26 'https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git',
27 'https://kernel.googlesource.com/pub/scm/linux/kernel/git/torvalds/linux.git',
28)
29
Stephen Boydb68c17a2019-09-26 15:08:02 -070030PATCHWORK_URLS = (
31 'https://lore.kernel.org/patchwork',
32 'https://patchwork.kernel.org',
33 'https://patchwork.ozlabs.org',
34 'https://patchwork.freedesktop.org',
35 'https://patchwork.linux-mips.org',
36)
37
Harry Cuttsae372f32019-02-12 18:01:14 -080038COMMIT_MESSAGE_WIDTH = 75
39
Brian Norris9f8a2be2018-06-01 11:14:08 -070040_PWCLIENTRC = os.path.expanduser('~/.pwclientrc')
41
Alexandru M Stan725c71f2019-12-11 16:53:33 -080042def _git(args, stdin=None, encoding='utf-8'):
43 """Calls a git subcommand.
44
45 Similar to subprocess.check_output.
46
47 Args:
48 stdin: a string or bytes (depending on encoding) that will be passed
49 to the git subcommand.
50 encoding: either 'utf-8' (default) or None. Override it to None if
51 you want both stdin and stdout to be raw bytes.
52
53 Returns:
54 the stdout of the git subcommand, same type as stdin. The output is
55 also run through strip to make sure there's no extra whitespace.
56
57 Raises:
58 subprocess.CalledProcessError: when return code is not zero.
59 The exception has a .returncode attribute.
60 """
61 return subprocess.run(
62 ['git'] + args,
63 encoding=encoding,
64 input=stdin,
65 stdout=subprocess.PIPE,
66 check=True,
67 ).stdout.strip()
68
69def _git_returncode(*args, **kwargs):
70 """Same as _git, but return returncode instead of stdout.
71
72 Similar to subprocess.call.
73
74 Never raises subprocess.CalledProcessError.
75 """
76 try:
77 _git(*args, **kwargs)
78 return 0
79 except subprocess.CalledProcessError as e:
80 return e.returncode
81
Guenter Roeckbdbb9cc2018-04-19 10:05:08 -070082def _get_conflicts():
83 """Report conflicting files."""
84 resolutions = ('DD', 'AU', 'UD', 'UA', 'DU', 'AA', 'UU')
85 conflicts = []
Alexandru M Stan725c71f2019-12-11 16:53:33 -080086 output = _git(['status', '--porcelain', '--untracked-files=no'])
87 for line in output.splitlines():
Douglas Anderson46287f92018-04-30 09:58:24 -070088 if not line:
Guenter Roeckbdbb9cc2018-04-19 10:05:08 -070089 continue
Douglas Anderson46287f92018-04-30 09:58:24 -070090 resolution, name = line.split(None, 1)
Guenter Roeckbdbb9cc2018-04-19 10:05:08 -070091 if resolution in resolutions:
92 conflicts.append(' ' + name)
93 if not conflicts:
Douglas Andersonb6a10fe2019-08-12 13:53:30 -070094 return ''
Guenter Roeckbdbb9cc2018-04-19 10:05:08 -070095 return '\nConflicts:\n%s\n' % '\n'.join(conflicts)
96
Guenter Roeckd66daa72018-04-19 10:31:25 -070097def _find_linux_remote():
98 """Find a remote pointing to a Linux upstream repository."""
Alexandru M Stan725c71f2019-12-11 16:53:33 -080099 for remote in _git(['remote']).splitlines():
100 try:
101 if _git(['remote', 'get-url', remote]) in LINUX_URLS:
102 return remote
103 except subprocess.CalledProcessError:
104 # Kinda weird, get-url failing on an item that git just gave us.
105 continue
Guenter Roeckd66daa72018-04-19 10:31:25 -0700106 return None
107
Guenter Roeckbdbb9cc2018-04-19 10:05:08 -0700108def _pause_for_merge(conflicts):
Alexandru M Stanfb5b5ee2014-12-04 13:32:55 -0800109 """Pause and go in the background till user resolves the conflicts."""
110
Alexandru M Stan725c71f2019-12-11 16:53:33 -0800111 git_root = _git(['rev-parse', '--show-toplevel'])
Alexandru M Stanfb5b5ee2014-12-04 13:32:55 -0800112
113 paths = (
114 os.path.join(git_root, '.git', 'rebase-apply'),
115 os.path.join(git_root, '.git', 'CHERRY_PICK_HEAD'),
116 )
117 for path in paths:
118 if os.path.exists(path):
Tzung-Bi Shih436fdba2019-09-04 19:05:00 +0800119 errprint('Found "%s".' % path)
120 errprint(conflicts)
121 errprint('Please resolve the conflicts and restart the '
122 'shell job when done. Kill this job if you '
123 'aborted the conflict.')
Alexandru M Stanfb5b5ee2014-12-04 13:32:55 -0800124 os.kill(os.getpid(), signal.SIGTSTP)
125 # TODO: figure out what the state is after the merging, and go based on
126 # that (should we abort? skip? continue?)
127 # Perhaps check last commit message to see if it's the one we were using.
128
Brian Norris9f8a2be2018-06-01 11:14:08 -0700129def _get_pw_url(project):
130 """Retrieve the patchwork server URL from .pwclientrc.
131
Mike Frysingerf80ca212018-07-13 15:02:52 -0400132 Args:
133 project: patchwork project name; if None, we retrieve the default
134 from pwclientrc
Brian Norris9f8a2be2018-06-01 11:14:08 -0700135 """
Alexandru M Stan725c71f2019-12-11 16:53:33 -0800136 config = configparser.ConfigParser()
Brian Norris9f8a2be2018-06-01 11:14:08 -0700137 config.read([_PWCLIENTRC])
138
139 if project is None:
140 try:
141 project = config.get('options', 'default')
Alexandru M Stan725c71f2019-12-11 16:53:33 -0800142 except (configparser.NoSectionError, configparser.NoOptionError) as e:
143 errprint('Error: no default patchwork project found in %s. (%r)'
144 % (_PWCLIENTRC, e))
Brian Norris9f8a2be2018-06-01 11:14:08 -0700145 sys.exit(1)
146
147 if not config.has_option(project, 'url'):
Tzung-Bi Shih436fdba2019-09-04 19:05:00 +0800148 errprint("Error: patchwork URL not found for project '%s'" % project)
Brian Norris9f8a2be2018-06-01 11:14:08 -0700149 sys.exit(1)
150
151 url = config.get(project, 'url')
Brian Norris2d4e9762018-08-15 13:11:47 -0700152 # Strip trailing 'xmlrpc' and/or trailing slash.
153 return re.sub('/(xmlrpc/)?$', '', url)
Brian Norris9f8a2be2018-06-01 11:14:08 -0700154
Harry Cuttsae372f32019-02-12 18:01:14 -0800155def _wrap_commit_line(prefix, content):
156 line = prefix + '=' + content
157 indent = ' ' * (len(prefix) + 1)
158 return textwrap.fill(line, COMMIT_MESSAGE_WIDTH, subsequent_indent=indent)
159
Stephen Boydb68c17a2019-09-26 15:08:02 -0700160def _pick_patchwork(url, patch_id, args):
Tzung-Bi Shih886c9092019-09-02 12:46:16 +0800161 if args['tag'] is None:
162 args['tag'] = 'FROMLIST: '
163
Alexandru M Stan725c71f2019-12-11 16:53:33 -0800164 opener = urllib.request.urlopen('%s/patch/%d/mbox' % (url, patch_id))
Tzung-Bi Shih886c9092019-09-02 12:46:16 +0800165 if opener.getcode() != 200:
Tzung-Bi Shih436fdba2019-09-04 19:05:00 +0800166 errprint('Error: could not download patch - error code %d'
167 % opener.getcode())
Tzung-Bi Shih886c9092019-09-02 12:46:16 +0800168 sys.exit(1)
169 patch_contents = opener.read()
170
171 if not patch_contents:
Tzung-Bi Shih436fdba2019-09-04 19:05:00 +0800172 errprint('Error: No patch content found')
Tzung-Bi Shih886c9092019-09-02 12:46:16 +0800173 sys.exit(1)
174
Douglas Andersonbecd4e62019-09-25 13:40:55 -0700175 message_id = mailbox.Message(patch_contents)['Message-Id']
176 message_id = re.sub('^<|>$', '', message_id.strip())
Tzung-Bi Shih886c9092019-09-02 12:46:16 +0800177 if args['source_line'] is None:
178 args['source_line'] = '(am from %s/patch/%d/)' % (url, patch_id)
Tzung-Bi Shiha8f310d2019-09-04 19:10:10 +0800179 args['source_line'] += (
180 '\n(also found at https://lkml.kernel.org/r/%s)' % message_id)
Tzung-Bi Shih886c9092019-09-02 12:46:16 +0800181
Douglas Andersonbecd4e62019-09-25 13:40:55 -0700182 # Auto-snarf the Change-Id if it was encoded into the Message-Id.
183 mo = re.match(r'.*(I[a-f0-9]{40})@changeid$', message_id)
184 if mo and args['changeid'] is None:
185 args['changeid'] = mo.group(1)
186
Tzung-Bi Shih886c9092019-09-02 12:46:16 +0800187 if args['replace']:
Alexandru M Stan725c71f2019-12-11 16:53:33 -0800188 _git(['reset', '--hard', 'HEAD~1'])
Tzung-Bi Shih886c9092019-09-02 12:46:16 +0800189
Alexandru M Stan725c71f2019-12-11 16:53:33 -0800190 return _git_returncode(['am', '-3'], stdin=patch_contents, encoding=None)
Tzung-Bi Shih886c9092019-09-02 12:46:16 +0800191
Stephen Boydb68c17a2019-09-26 15:08:02 -0700192def _match_patchwork(match, args):
193 """Match location: pw://### or pw://PROJECT/###."""
194 pw_project = match.group(2)
195 patch_id = int(match.group(3))
196
197 if args['debug']:
198 print('_match_patchwork: pw_project=%s, patch_id=%d' %
199 (pw_project, patch_id))
200
201 url = _get_pw_url(pw_project)
202 return _pick_patchwork(url, patch_id, args)
203
204def _match_msgid(match, args):
205 """Match location: msgid://MSGID."""
206 msgid = match.group(1)
207
208 if args['debug']:
209 print('_match_msgid: message_id=%s' % (msgid))
210
211 # Patchwork requires the brackets so force it
212 msgid = '<' + msgid + '>'
213 url = None
214 for url in PATCHWORK_URLS:
Alexandru M Stan725c71f2019-12-11 16:53:33 -0800215 rpc = xmlrpc.client.ServerProxy(url + '/xmlrpc/')
Stephen Boydb68c17a2019-09-26 15:08:02 -0700216 res = rpc.patch_list({'msgid': msgid})
217 if res:
218 patch_id = res[0]['id']
219 break
220 else:
221 errprint('Error: could not find patch based on message id')
222 sys.exit(1)
223
224 return _pick_patchwork(url, patch_id, args)
225
Tzung-Bi Shih886c9092019-09-02 12:46:16 +0800226def _match_linux(match, args):
227 """Match location: linux://HASH."""
228 commit = match.group(1)
229
230 if args['debug']:
231 print('_match_linux: commit=%s' % commit)
232
233 # Confirm a 'linux' remote is setup.
234 linux_remote = _find_linux_remote()
235 if not linux_remote:
Tzung-Bi Shih436fdba2019-09-04 19:05:00 +0800236 errprint('Error: need a valid upstream remote')
Tzung-Bi Shih886c9092019-09-02 12:46:16 +0800237 sys.exit(1)
238
239 linux_master = '%s/master' % linux_remote
Alexandru M Stan725c71f2019-12-11 16:53:33 -0800240 try:
241 _git(['merge-base', '--is-ancestor', commit, linux_master])
242 except subprocess.CalledProcessError:
Tzung-Bi Shih436fdba2019-09-04 19:05:00 +0800243 errprint('Error: Commit not in %s' % linux_master)
Tzung-Bi Shih886c9092019-09-02 12:46:16 +0800244 sys.exit(1)
245
246 if args['source_line'] is None:
Alexandru M Stan725c71f2019-12-11 16:53:33 -0800247 commit = _git(['rev-parse', commit])
Tzung-Bi Shih886c9092019-09-02 12:46:16 +0800248 args['source_line'] = ('(cherry picked from commit %s)' %
249 (commit))
250 if args['tag'] is None:
251 args['tag'] = 'UPSTREAM: '
252
253 if args['replace']:
Alexandru M Stan725c71f2019-12-11 16:53:33 -0800254 _git(['reset', '--hard', 'HEAD~1'])
Tzung-Bi Shih886c9092019-09-02 12:46:16 +0800255
Alexandru M Stan725c71f2019-12-11 16:53:33 -0800256 return _git_returncode(['cherry-pick', commit])
Tzung-Bi Shih886c9092019-09-02 12:46:16 +0800257
258def _match_fromgit(match, args):
259 """Match location: git://remote/branch/HASH."""
260 remote = match.group(2)
261 branch = match.group(3)
262 commit = match.group(4)
263
264 if args['debug']:
265 print('_match_fromgit: remote=%s branch=%s commit=%s' %
266 (remote, branch, commit))
267
Alexandru M Stan725c71f2019-12-11 16:53:33 -0800268 try:
269 _git(['merge-base', '--is-ancestor', commit,
270 '%s/%s' % (remote, branch)])
271 except subprocess.CalledProcessError:
Tzung-Bi Shih436fdba2019-09-04 19:05:00 +0800272 errprint('Error: Commit not in %s/%s' % (remote, branch))
Tzung-Bi Shih886c9092019-09-02 12:46:16 +0800273 sys.exit(1)
274
Alexandru M Stan725c71f2019-12-11 16:53:33 -0800275 url = _git(['remote', 'get-url', remote])
Tzung-Bi Shih886c9092019-09-02 12:46:16 +0800276
277 if args['source_line'] is None:
Alexandru M Stan725c71f2019-12-11 16:53:33 -0800278 commit = _git(['rev-parse', commit])
Tzung-Bi Shiha8f310d2019-09-04 19:10:10 +0800279 args['source_line'] = (
280 '(cherry picked from commit %s\n %s %s)' % (commit, url, branch))
Tzung-Bi Shih886c9092019-09-02 12:46:16 +0800281 if args['tag'] is None:
282 args['tag'] = 'FROMGIT: '
283
284 if args['replace']:
Alexandru M Stan725c71f2019-12-11 16:53:33 -0800285 _git(['reset', '--hard', 'HEAD~1'])
Tzung-Bi Shih886c9092019-09-02 12:46:16 +0800286
Alexandru M Stan725c71f2019-12-11 16:53:33 -0800287 return _git_returncode(['cherry-pick', commit])
Tzung-Bi Shih886c9092019-09-02 12:46:16 +0800288
289def _match_gitfetch(match, args):
290 """Match location: (git|https)://repoURL#branch/HASH."""
291 remote = match.group(1)
292 branch = match.group(3)
293 commit = match.group(4)
294
295 if args['debug']:
296 print('_match_gitfetch: remote=%s branch=%s commit=%s' %
297 (remote, branch, commit))
298
Alexandru M Stan725c71f2019-12-11 16:53:33 -0800299 try:
300 _git(['fetch', remote, branch])
301 except subprocess.CalledProcessError:
Tzung-Bi Shih436fdba2019-09-04 19:05:00 +0800302 errprint('Error: Branch not in %s' % remote)
Tzung-Bi Shih886c9092019-09-02 12:46:16 +0800303 sys.exit(1)
304
305 url = remote
306
307 if args['source_line'] is None:
Alexandru M Stan725c71f2019-12-11 16:53:33 -0800308 commit = _git(['rev-parse', commit])
Tzung-Bi Shiha8f310d2019-09-04 19:10:10 +0800309 args['source_line'] = (
310 '(cherry picked from commit %s\n %s %s)' % (commit, url, branch))
Tzung-Bi Shih886c9092019-09-02 12:46:16 +0800311 if args['tag'] is None:
312 args['tag'] = 'FROMGIT: '
313
Stephen Boyd4b3869a2020-01-24 15:35:37 -0800314 if args['replace']:
315 _git(['reset', '--hard', 'HEAD~1'])
316
Alexandru M Stan725c71f2019-12-11 16:53:33 -0800317 return _git_returncode(['cherry-pick', commit])
Tzung-Bi Shih886c9092019-09-02 12:46:16 +0800318
Stephen Boyd96396032020-02-25 10:12:59 -0800319def _match_gitweb(match, args):
320 """Match location: https://repoURL/commit/?h=branch&id=HASH."""
321 remote = match.group(1)
322 branch = match.group(2)
323 commit = match.group(3)
324
325 if args['debug']:
326 print('_match_gitweb: remote=%s branch=%s commit=%s' %
327 (remote, branch, commit))
328
329 try:
330 _git(['fetch', remote, branch])
331 except subprocess.CalledProcessError:
332 errprint('Error: Branch not in %s' % remote)
333 sys.exit(1)
334
335 url = remote
336
337 if args['source_line'] is None:
338 commit = _git(['rev-parse', commit])
339 args['source_line'] = (
340 '(cherry picked from commit %s\n %s %s)' % (commit, url, branch))
341 if args['tag'] is None:
342 args['tag'] = 'FROMGIT: '
343
344 if args['replace']:
345 _git(['reset', '--hard', 'HEAD~1'])
346
347 return _git_returncode(['cherry-pick', commit])
348
Alexandru M Stanfb5b5ee2014-12-04 13:32:55 -0800349def main(args):
350 """This is the main entrypoint for fromupstream.
351
352 Args:
353 args: sys.argv[1:]
354
355 Returns:
356 An int return code.
357 """
358 parser = argparse.ArgumentParser()
359
360 parser.add_argument('--bug', '-b',
Guenter Roeckf47a50c2018-07-25 12:41:36 -0700361 type=str, help='BUG= line')
Alexandru M Stanfb5b5ee2014-12-04 13:32:55 -0800362 parser.add_argument('--test', '-t',
Guenter Roeckf47a50c2018-07-25 12:41:36 -0700363 type=str, help='TEST= line')
Stephen Boyd24b309b2018-11-06 22:11:00 -0800364 parser.add_argument('--crbug', action='append',
365 type=int, help='BUG=chromium: line')
366 parser.add_argument('--buganizer', action='append',
367 type=int, help='BUG=b: line')
Alexandru M Stanfb5b5ee2014-12-04 13:32:55 -0800368 parser.add_argument('--changeid', '-c',
369 help='Overrides the gerrit generated Change-Id line')
370
Tzung-Bi Shihf5d25a82019-09-02 11:40:09 +0800371 parser.add_argument('--replace', '-r',
Alexandru M Stanfb5b5ee2014-12-04 13:32:55 -0800372 action='store_true',
Tzung-Bi Shih196d31e2019-09-01 18:16:28 +0800373 help='Replaces the HEAD commit with this one, taking '
374 'its properties(BUG, TEST, Change-Id). Useful for '
Alexandru M Stanfb5b5ee2014-12-04 13:32:55 -0800375 'updating commits.')
376 parser.add_argument('--nosignoff',
377 dest='signoff', action='store_false')
Tzung-Bi Shih5100c742019-09-02 10:28:32 +0800378 parser.add_argument('--debug', '-d', action='store_true',
379 help='Prints more verbose logs.')
Alexandru M Stanfb5b5ee2014-12-04 13:32:55 -0800380
381 parser.add_argument('--tag',
382 help='Overrides the tag from the title')
383 parser.add_argument('--source', '-s',
384 dest='source_line', type=str,
Tzung-Bi Shih196d31e2019-09-01 18:16:28 +0800385 help='Overrides the source line, last line, ex: '
Alexandru M Stanfb5b5ee2014-12-04 13:32:55 -0800386 '(am from http://....)')
387 parser.add_argument('locations',
Douglas Andersonc77a8b82018-05-04 17:02:03 -0700388 nargs='+',
Tzung-Bi Shih196d31e2019-09-01 18:16:28 +0800389 help='Patchwork ID (pw://### or pw://PROJECT/###, '
390 'where PROJECT is defined in ~/.pwclientrc; if no '
391 'PROJECT is specified, the default is retrieved from '
392 '~/.pwclientrc), '
Stephen Boydb68c17a2019-09-26 15:08:02 -0700393 'Message-ID (msgid://MSGID), '
Tzung-Bi Shih196d31e2019-09-01 18:16:28 +0800394 'linux commit like linux://HASH, or '
395 'git reference like git://remote/branch/HASH or '
396 'git://repoURL#branch/HASH or '
Stephen Boyd96396032020-02-25 10:12:59 -0800397 'https://repoURL#branch/HASH or '
398 'https://repoURL/commit/?h=branch&id=HASH')
Alexandru M Stanfb5b5ee2014-12-04 13:32:55 -0800399
400 args = vars(parser.parse_args(args))
401
Stephen Boyd24b309b2018-11-06 22:11:00 -0800402 buglist = [args['bug']] if args['bug'] else []
403 if args['buganizer']:
404 buglist += ['b:{0}'.format(x) for x in args['buganizer']]
405 if args['crbug']:
406 buglist += ['chromium:{0}'.format(x) for x in args['crbug']]
Brian Norris667a0cb2018-12-07 09:28:46 -0800407 if buglist:
408 args['bug'] = ', '.join(buglist)
Stephen Boyd24b309b2018-11-06 22:11:00 -0800409
Alexandru M Stanfb5b5ee2014-12-04 13:32:55 -0800410 if args['replace']:
Alexandru M Stan725c71f2019-12-11 16:53:33 -0800411 old_commit_message = _git(['show', '-s', '--format=%B', 'HEAD'])
Tzung-Bi Shih231fada2019-09-02 00:54:59 +0800412
413 # It is possible that multiple Change-Ids are in the commit message
414 # (due to cherry picking). We only want to pull out the first one.
415 changeid_match = re.search('^Change-Id: (.*)$',
416 old_commit_message, re.MULTILINE)
417 if changeid_match:
418 args['changeid'] = changeid_match.group(1)
419
Tzung-Bi Shihe1e0e7d2019-09-02 15:04:38 +0800420 bugs = re.findall('^BUG=(.*)$', old_commit_message, re.MULTILINE)
Tzung-Bi Shih04345302019-09-02 12:04:01 +0800421 if args['bug'] is None and bugs:
422 args['bug'] = '\nBUG='.join(bugs)
423
Tzung-Bi Shihe1e0e7d2019-09-02 15:04:38 +0800424 tests = re.findall('^TEST=(.*)$', old_commit_message, re.MULTILINE)
Tzung-Bi Shih04345302019-09-02 12:04:01 +0800425 if args['test'] is None and tests:
426 args['test'] = '\nTEST='.join(tests)
Alexandru M Stanfb5b5ee2014-12-04 13:32:55 -0800427 # TODO: deal with multiline BUG/TEST better
Alexandru M Stanfb5b5ee2014-12-04 13:32:55 -0800428
Guenter Roeckf47a50c2018-07-25 12:41:36 -0700429 if args['bug'] is None or args['test'] is None:
Tzung-Bi Shih196d31e2019-09-01 18:16:28 +0800430 parser.error('BUG=/TEST= lines are required; --replace can help '
Stephen Boyde6fdf912018-11-09 10:30:57 -0800431 'automate, or set via --bug/--test')
Guenter Roeckf47a50c2018-07-25 12:41:36 -0700432
Tzung-Bi Shih5100c742019-09-02 10:28:32 +0800433 if args['debug']:
434 pprint.pprint(args)
435
Tzung-Bi Shih886c9092019-09-02 12:46:16 +0800436 re_matches = (
Tzung-Bi Shihe1e0e7d2019-09-02 15:04:38 +0800437 (re.compile(r'^pw://(([^/]+)/)?(\d+)'), _match_patchwork),
Stephen Boydb68c17a2019-09-26 15:08:02 -0700438 (re.compile(r'^msgid://<?([^>]*)>?'), _match_msgid),
Tzung-Bi Shihe1e0e7d2019-09-02 15:04:38 +0800439 (re.compile(r'^linux://([0-9a-f]+)'), _match_linux),
440 (re.compile(r'^(from)?git://([^/\#]+)/([^#]+)/([0-9a-f]+)$'),
Tzung-Bi Shih886c9092019-09-02 12:46:16 +0800441 _match_fromgit),
Tzung-Bi Shihe1e0e7d2019-09-02 15:04:38 +0800442 (re.compile(r'^((git|https)://.+)#(.+)/([0-9a-f]+)$'), _match_gitfetch),
Stephen Boyd96396032020-02-25 10:12:59 -0800443 (re.compile(r'^(https://.+)/commit/\?h=(.+)\&id=([0-9a-f]+)$'), _match_gitweb),
Tzung-Bi Shih886c9092019-09-02 12:46:16 +0800444 )
Alexandru M Stanfb5b5ee2014-12-04 13:32:55 -0800445
Tzung-Bi Shih886c9092019-09-02 12:46:16 +0800446 for location in args['locations']:
Tzung-Bi Shih5100c742019-09-02 10:28:32 +0800447 if args['debug']:
448 print('location=%s' % location)
449
Tzung-Bi Shih886c9092019-09-02 12:46:16 +0800450 for reg, handler in re_matches:
451 match = reg.match(location)
452 if match:
453 ret = handler(match, args)
454 break
Alexandru M Stanfb5b5ee2014-12-04 13:32:55 -0800455 else:
Tzung-Bi Shih436fdba2019-09-04 19:05:00 +0800456 errprint('Don\'t know what "%s" means.' % location)
Alexandru M Stanfb5b5ee2014-12-04 13:32:55 -0800457 sys.exit(1)
458
459 if ret != 0:
Guenter Roeckbdbb9cc2018-04-19 10:05:08 -0700460 conflicts = _get_conflicts()
Douglas Anderson2108e532018-04-30 09:50:42 -0700461 if args['tag'] == 'UPSTREAM: ':
462 args['tag'] = 'BACKPORT: '
463 else:
464 args['tag'] = 'BACKPORT: ' + args['tag']
Guenter Roeckbdbb9cc2018-04-19 10:05:08 -0700465 _pause_for_merge(conflicts)
466 else:
Douglas Andersonb6a10fe2019-08-12 13:53:30 -0700467 conflicts = ''
Alexandru M Stanfb5b5ee2014-12-04 13:32:55 -0800468
469 # extract commit message
Alexandru M Stan725c71f2019-12-11 16:53:33 -0800470 commit_message = _git(['show', '-s', '--format=%B', 'HEAD'])
Alexandru M Stanfb5b5ee2014-12-04 13:32:55 -0800471
Guenter Roeck2e4f2512018-04-24 09:20:51 -0700472 # Remove stray Change-Id, most likely from merge resolution
473 commit_message = re.sub(r'Change-Id:.*\n?', '', commit_message)
474
Brian Norris7a41b982018-06-01 10:28:29 -0700475 # Note the source location before tagging anything else
476 commit_message += '\n' + args['source_line']
477
Alexandru M Stanfb5b5ee2014-12-04 13:32:55 -0800478 # add automatic Change ID, BUG, and TEST (and maybe signoff too) so
479 # next commands know where to work on
480 commit_message += '\n'
Guenter Roeckbdbb9cc2018-04-19 10:05:08 -0700481 commit_message += conflicts
Alexandru M Stanfb5b5ee2014-12-04 13:32:55 -0800482 commit_message += '\n' + 'BUG=' + args['bug']
Harry Cuttsae372f32019-02-12 18:01:14 -0800483 commit_message += '\n' + _wrap_commit_line('TEST', args['test'])
Alexandru M Stanfb5b5ee2014-12-04 13:32:55 -0800484 if args['signoff']:
485 extra = ['-s']
486 else:
487 extra = []
Alexandru M Stan725c71f2019-12-11 16:53:33 -0800488 _git(['commit'] + extra + ['--amend', '-F', '-'], stdin=commit_message)
Alexandru M Stanfb5b5ee2014-12-04 13:32:55 -0800489
490 # re-extract commit message
Alexandru M Stan725c71f2019-12-11 16:53:33 -0800491 commit_message = _git(['show', '-s', '--format=%B', 'HEAD'])
Alexandru M Stanfb5b5ee2014-12-04 13:32:55 -0800492
Douglas Andersonbecd4e62019-09-25 13:40:55 -0700493 # If we see a "Link: " that seems to point to a Message-Id with an
494 # automatic Change-Id we'll snarf it out.
495 mo = re.search(r'^Link:.*(I[a-f0-9]{40})@changeid', commit_message,
496 re.MULTILINE)
497 if mo and args['changeid'] is None:
498 args['changeid'] = mo.group(1)
499
Alexandru M Stanfb5b5ee2014-12-04 13:32:55 -0800500 # replace changeid if needed
501 if args['changeid'] is not None:
502 commit_message = re.sub(r'(Change-Id: )(\w+)', r'\1%s' %
503 args['changeid'], commit_message)
504 args['changeid'] = None
505
506 # decorate it that it's from outside
507 commit_message = args['tag'] + commit_message
Alexandru M Stanfb5b5ee2014-12-04 13:32:55 -0800508
509 # commit everything
Alexandru M Stan725c71f2019-12-11 16:53:33 -0800510 _git(['commit', '--amend', '-F', '-'], stdin=commit_message)
Alexandru M Stanfb5b5ee2014-12-04 13:32:55 -0800511
Chirantan Ekbote4b08e712019-06-12 15:35:41 +0900512 return 0
Alexandru M Stanfb5b5ee2014-12-04 13:32:55 -0800513
514if __name__ == '__main__':
515 sys.exit(main(sys.argv[1:]))