blob: db2875f1f86c7edd451f4456206f2013e83120f6 [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
Alexandru M Stanfb5b5ee2014-12-04 13:32:55 -080026LINUX_URLS = (
27 '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',
30)
31
Stephen Boydb68c17a2019-09-26 15:08:02 -070032PATCHWORK_URLS = (
33 'https://lore.kernel.org/patchwork',
34 'https://patchwork.kernel.org',
35 'https://patchwork.ozlabs.org',
36 'https://patchwork.freedesktop.org',
37 'https://patchwork.linux-mips.org',
38)
39
Harry Cuttsae372f32019-02-12 18:01:14 -080040COMMIT_MESSAGE_WIDTH = 75
41
Brian Norris9f8a2be2018-06-01 11:14:08 -070042_PWCLIENTRC = os.path.expanduser('~/.pwclientrc')
43
Alexandru M Stan725c71f2019-12-11 16:53:33 -080044def _git(args, stdin=None, encoding='utf-8'):
45 """Calls a git subcommand.
46
47 Similar to subprocess.check_output.
48
49 Args:
Brian Norris6baeb2e2020-03-18 12:13:30 -070050 args: subcommand + args passed to 'git'.
Alexandru M Stan725c71f2019-12-11 16:53:33 -080051 stdin: a string or bytes (depending on encoding) that will be passed
52 to the git subcommand.
53 encoding: either 'utf-8' (default) or None. Override it to None if
54 you want both stdin and stdout to be raw bytes.
55
56 Returns:
57 the stdout of the git subcommand, same type as stdin. The output is
58 also run through strip to make sure there's no extra whitespace.
59
60 Raises:
61 subprocess.CalledProcessError: when return code is not zero.
62 The exception has a .returncode attribute.
63 """
64 return subprocess.run(
65 ['git'] + args,
66 encoding=encoding,
67 input=stdin,
68 stdout=subprocess.PIPE,
69 check=True,
70 ).stdout.strip()
71
72def _git_returncode(*args, **kwargs):
73 """Same as _git, but return returncode instead of stdout.
74
75 Similar to subprocess.call.
76
77 Never raises subprocess.CalledProcessError.
78 """
79 try:
80 _git(*args, **kwargs)
81 return 0
82 except subprocess.CalledProcessError as e:
83 return e.returncode
84
Guenter Roeckbdbb9cc2018-04-19 10:05:08 -070085def _get_conflicts():
86 """Report conflicting files."""
87 resolutions = ('DD', 'AU', 'UD', 'UA', 'DU', 'AA', 'UU')
88 conflicts = []
Alexandru M Stan725c71f2019-12-11 16:53:33 -080089 output = _git(['status', '--porcelain', '--untracked-files=no'])
90 for line in output.splitlines():
Douglas Anderson46287f92018-04-30 09:58:24 -070091 if not line:
Guenter Roeckbdbb9cc2018-04-19 10:05:08 -070092 continue
Douglas Anderson46287f92018-04-30 09:58:24 -070093 resolution, name = line.split(None, 1)
Guenter Roeckbdbb9cc2018-04-19 10:05:08 -070094 if resolution in resolutions:
95 conflicts.append(' ' + name)
96 if not conflicts:
Douglas Andersonb6a10fe2019-08-12 13:53:30 -070097 return ''
Guenter Roeckbdbb9cc2018-04-19 10:05:08 -070098 return '\nConflicts:\n%s\n' % '\n'.join(conflicts)
99
Guenter Roeckd66daa72018-04-19 10:31:25 -0700100def _find_linux_remote():
101 """Find a remote pointing to a Linux upstream repository."""
Alexandru M Stan725c71f2019-12-11 16:53:33 -0800102 for remote in _git(['remote']).splitlines():
103 try:
104 if _git(['remote', 'get-url', remote]) in LINUX_URLS:
105 return remote
106 except subprocess.CalledProcessError:
107 # Kinda weird, get-url failing on an item that git just gave us.
108 continue
Guenter Roeckd66daa72018-04-19 10:31:25 -0700109 return None
110
Guenter Roeckbdbb9cc2018-04-19 10:05:08 -0700111def _pause_for_merge(conflicts):
Alexandru M Stanfb5b5ee2014-12-04 13:32:55 -0800112 """Pause and go in the background till user resolves the conflicts."""
113
Alexandru M Stan725c71f2019-12-11 16:53:33 -0800114 git_root = _git(['rev-parse', '--show-toplevel'])
Harry Cutts2bcd9af2020-02-20 16:27:50 -0800115 previous_head_hash = _git(['rev-parse', 'HEAD'])
Alexandru M Stanfb5b5ee2014-12-04 13:32:55 -0800116
117 paths = (
118 os.path.join(git_root, '.git', 'rebase-apply'),
119 os.path.join(git_root, '.git', 'CHERRY_PICK_HEAD'),
120 )
121 for path in paths:
122 if os.path.exists(path):
Tzung-Bi Shih436fdba2019-09-04 19:05:00 +0800123 errprint('Found "%s".' % path)
124 errprint(conflicts)
125 errprint('Please resolve the conflicts and restart the '
126 'shell job when done. Kill this job if you '
127 'aborted the conflict.')
Alexandru M Stanfb5b5ee2014-12-04 13:32:55 -0800128 os.kill(os.getpid(), signal.SIGTSTP)
Harry Cutts2bcd9af2020-02-20 16:27:50 -0800129
130 # Check the conflicts actually got resolved. Otherwise we'll end up
131 # modifying the wrong commit message and probably confusing people.
132 while previous_head_hash == _git(['rev-parse', 'HEAD']):
133 errprint('Error: no new commit has been made. Did you forget to run '
134 '`git am --continue` or `git cherry-pick --continue`?')
135 errprint('Please create a new commit and restart the shell job (or kill'
136 ' it if you aborted the conflict).')
137 os.kill(os.getpid(), signal.SIGTSTP)
Alexandru M Stanfb5b5ee2014-12-04 13:32:55 -0800138
Brian Norris9f8a2be2018-06-01 11:14:08 -0700139def _get_pw_url(project):
140 """Retrieve the patchwork server URL from .pwclientrc.
141
Mike Frysingerf80ca212018-07-13 15:02:52 -0400142 Args:
143 project: patchwork project name; if None, we retrieve the default
144 from pwclientrc
Brian Norris9f8a2be2018-06-01 11:14:08 -0700145 """
Alexandru M Stan725c71f2019-12-11 16:53:33 -0800146 config = configparser.ConfigParser()
Brian Norris9f8a2be2018-06-01 11:14:08 -0700147 config.read([_PWCLIENTRC])
148
149 if project is None:
150 try:
151 project = config.get('options', 'default')
Alexandru M Stan725c71f2019-12-11 16:53:33 -0800152 except (configparser.NoSectionError, configparser.NoOptionError) as e:
153 errprint('Error: no default patchwork project found in %s. (%r)'
154 % (_PWCLIENTRC, e))
Brian Norris9f8a2be2018-06-01 11:14:08 -0700155 sys.exit(1)
156
157 if not config.has_option(project, 'url'):
Tzung-Bi Shih436fdba2019-09-04 19:05:00 +0800158 errprint("Error: patchwork URL not found for project '%s'" % project)
Brian Norris9f8a2be2018-06-01 11:14:08 -0700159 sys.exit(1)
160
161 url = config.get(project, 'url')
Brian Norris2d4e9762018-08-15 13:11:47 -0700162 # Strip trailing 'xmlrpc' and/or trailing slash.
163 return re.sub('/(xmlrpc/)?$', '', url)
Brian Norris9f8a2be2018-06-01 11:14:08 -0700164
Harry Cuttsae372f32019-02-12 18:01:14 -0800165def _wrap_commit_line(prefix, content):
166 line = prefix + '=' + content
167 indent = ' ' * (len(prefix) + 1)
168 return textwrap.fill(line, COMMIT_MESSAGE_WIDTH, subsequent_indent=indent)
169
Stephen Boydb68c17a2019-09-26 15:08:02 -0700170def _pick_patchwork(url, patch_id, args):
Tzung-Bi Shih886c9092019-09-02 12:46:16 +0800171 if args['tag'] is None:
172 args['tag'] = 'FROMLIST: '
173
Brian Norris8553f032020-03-18 11:59:02 -0700174 try:
175 opener = urllib.request.urlopen('%s/patch/%d/mbox' % (url, patch_id))
176 except urllib.error.HTTPError as e:
177 errprint('Error: could not download patch: %s' % e)
Tzung-Bi Shih886c9092019-09-02 12:46:16 +0800178 sys.exit(1)
179 patch_contents = opener.read()
180
181 if not patch_contents:
Tzung-Bi Shih436fdba2019-09-04 19:05:00 +0800182 errprint('Error: No patch content found')
Tzung-Bi Shih886c9092019-09-02 12:46:16 +0800183 sys.exit(1)
184
Douglas Andersonbecd4e62019-09-25 13:40:55 -0700185 message_id = mailbox.Message(patch_contents)['Message-Id']
186 message_id = re.sub('^<|>$', '', message_id.strip())
Tzung-Bi Shih886c9092019-09-02 12:46:16 +0800187 if args['source_line'] is None:
188 args['source_line'] = '(am from %s/patch/%d/)' % (url, patch_id)
Brian Norris8553f032020-03-18 11:59:02 -0700189 for url_template in [
190 'https://lkml.kernel.org/r/%s',
191 # hostap project (and others) are here, but not kernel.org.
192 'https://marc.info/?i=%s',
193 # public-inbox comes last as a "default"; it has a nice error page
194 # pointing to other redirectors, even if it doesn't have what
195 # you're looking for directly.
196 'https://public-inbox.org/git/%s',
197 ]:
198 alt_url = url_template % message_id
199 if args['debug']:
200 print('Probing archive for message at: %s' % alt_url)
201 try:
202 urllib.request.urlopen(alt_url)
203 except urllib.error.HTTPError as e:
204 # Skip all HTTP errors. We can expect 404 for archives that
205 # don't have this MessageId, or 300 for public-inbox ("not
206 # found, but try these other redirects"). It's less clear what
207 # to do with transitory (or is it permanent?) server failures.
208 if args['debug']:
209 print('Skipping URL %s, error: %s' % (alt_url, e))
210 continue
211 # Success!
212 if args['debug']:
213 print('Found at %s' % alt_url)
214 break
215 else:
216 errprint(
217 "WARNING: couldn't find working MessageId URL; "
218 'defaulting to "%s"' % alt_url)
219 args['source_line'] += '\n(also found at %s)' % alt_url
Tzung-Bi Shih886c9092019-09-02 12:46:16 +0800220
Douglas Andersonbecd4e62019-09-25 13:40:55 -0700221 # Auto-snarf the Change-Id if it was encoded into the Message-Id.
222 mo = re.match(r'.*(I[a-f0-9]{40})@changeid$', message_id)
223 if mo and args['changeid'] is None:
224 args['changeid'] = mo.group(1)
225
Tzung-Bi Shih886c9092019-09-02 12:46:16 +0800226 if args['replace']:
Alexandru M Stan725c71f2019-12-11 16:53:33 -0800227 _git(['reset', '--hard', 'HEAD~1'])
Tzung-Bi Shih886c9092019-09-02 12:46:16 +0800228
Alexandru M Stan725c71f2019-12-11 16:53:33 -0800229 return _git_returncode(['am', '-3'], stdin=patch_contents, encoding=None)
Tzung-Bi Shih886c9092019-09-02 12:46:16 +0800230
Stephen Boydb68c17a2019-09-26 15:08:02 -0700231def _match_patchwork(match, args):
232 """Match location: pw://### or pw://PROJECT/###."""
233 pw_project = match.group(2)
234 patch_id = int(match.group(3))
235
236 if args['debug']:
237 print('_match_patchwork: pw_project=%s, patch_id=%d' %
238 (pw_project, patch_id))
239
240 url = _get_pw_url(pw_project)
241 return _pick_patchwork(url, patch_id, args)
242
243def _match_msgid(match, args):
244 """Match location: msgid://MSGID."""
245 msgid = match.group(1)
246
247 if args['debug']:
248 print('_match_msgid: message_id=%s' % (msgid))
249
250 # Patchwork requires the brackets so force it
251 msgid = '<' + msgid + '>'
252 url = None
253 for url in PATCHWORK_URLS:
Alexandru M Stan725c71f2019-12-11 16:53:33 -0800254 rpc = xmlrpc.client.ServerProxy(url + '/xmlrpc/')
Stephen Boydb68c17a2019-09-26 15:08:02 -0700255 res = rpc.patch_list({'msgid': msgid})
256 if res:
257 patch_id = res[0]['id']
258 break
259 else:
260 errprint('Error: could not find patch based on message id')
261 sys.exit(1)
262
263 return _pick_patchwork(url, patch_id, args)
264
Tzung-Bi Shih886c9092019-09-02 12:46:16 +0800265def _match_linux(match, args):
266 """Match location: linux://HASH."""
267 commit = match.group(1)
268
269 if args['debug']:
270 print('_match_linux: commit=%s' % commit)
271
272 # Confirm a 'linux' remote is setup.
273 linux_remote = _find_linux_remote()
274 if not linux_remote:
Tzung-Bi Shih436fdba2019-09-04 19:05:00 +0800275 errprint('Error: need a valid upstream remote')
Tzung-Bi Shih886c9092019-09-02 12:46:16 +0800276 sys.exit(1)
277
278 linux_master = '%s/master' % linux_remote
Alexandru M Stan725c71f2019-12-11 16:53:33 -0800279 try:
280 _git(['merge-base', '--is-ancestor', commit, linux_master])
281 except subprocess.CalledProcessError:
Tzung-Bi Shih436fdba2019-09-04 19:05:00 +0800282 errprint('Error: Commit not in %s' % linux_master)
Tzung-Bi Shih886c9092019-09-02 12:46:16 +0800283 sys.exit(1)
284
285 if args['source_line'] is None:
Alexandru M Stan725c71f2019-12-11 16:53:33 -0800286 commit = _git(['rev-parse', commit])
Tzung-Bi Shih886c9092019-09-02 12:46:16 +0800287 args['source_line'] = ('(cherry picked from commit %s)' %
288 (commit))
289 if args['tag'] is None:
290 args['tag'] = 'UPSTREAM: '
291
292 if args['replace']:
Alexandru M Stan725c71f2019-12-11 16:53:33 -0800293 _git(['reset', '--hard', 'HEAD~1'])
Tzung-Bi Shih886c9092019-09-02 12:46:16 +0800294
Alexandru M Stan725c71f2019-12-11 16:53:33 -0800295 return _git_returncode(['cherry-pick', commit])
Tzung-Bi Shih886c9092019-09-02 12:46:16 +0800296
297def _match_fromgit(match, args):
298 """Match location: git://remote/branch/HASH."""
299 remote = match.group(2)
300 branch = match.group(3)
301 commit = match.group(4)
302
303 if args['debug']:
304 print('_match_fromgit: remote=%s branch=%s commit=%s' %
305 (remote, branch, commit))
306
Alexandru M Stan725c71f2019-12-11 16:53:33 -0800307 try:
308 _git(['merge-base', '--is-ancestor', commit,
309 '%s/%s' % (remote, branch)])
310 except subprocess.CalledProcessError:
Tzung-Bi Shih436fdba2019-09-04 19:05:00 +0800311 errprint('Error: Commit not in %s/%s' % (remote, branch))
Tzung-Bi Shih886c9092019-09-02 12:46:16 +0800312 sys.exit(1)
313
Alexandru M Stan725c71f2019-12-11 16:53:33 -0800314 url = _git(['remote', 'get-url', remote])
Tzung-Bi Shih886c9092019-09-02 12:46:16 +0800315
316 if args['source_line'] is None:
Alexandru M Stan725c71f2019-12-11 16:53:33 -0800317 commit = _git(['rev-parse', commit])
Tzung-Bi Shiha8f310d2019-09-04 19:10:10 +0800318 args['source_line'] = (
319 '(cherry picked from commit %s\n %s %s)' % (commit, url, branch))
Tzung-Bi Shih886c9092019-09-02 12:46:16 +0800320 if args['tag'] is None:
321 args['tag'] = 'FROMGIT: '
322
323 if args['replace']:
Alexandru M Stan725c71f2019-12-11 16:53:33 -0800324 _git(['reset', '--hard', 'HEAD~1'])
Tzung-Bi Shih886c9092019-09-02 12:46:16 +0800325
Alexandru M Stan725c71f2019-12-11 16:53:33 -0800326 return _git_returncode(['cherry-pick', commit])
Tzung-Bi Shih886c9092019-09-02 12:46:16 +0800327
328def _match_gitfetch(match, args):
329 """Match location: (git|https)://repoURL#branch/HASH."""
330 remote = match.group(1)
331 branch = match.group(3)
332 commit = match.group(4)
333
334 if args['debug']:
335 print('_match_gitfetch: remote=%s branch=%s commit=%s' %
336 (remote, branch, commit))
337
Alexandru M Stan725c71f2019-12-11 16:53:33 -0800338 try:
339 _git(['fetch', remote, branch])
340 except subprocess.CalledProcessError:
Tzung-Bi Shih436fdba2019-09-04 19:05:00 +0800341 errprint('Error: Branch not in %s' % remote)
Tzung-Bi Shih886c9092019-09-02 12:46:16 +0800342 sys.exit(1)
343
344 url = remote
345
346 if args['source_line'] is None:
Alexandru M Stan725c71f2019-12-11 16:53:33 -0800347 commit = _git(['rev-parse', commit])
Tzung-Bi Shiha8f310d2019-09-04 19:10:10 +0800348 args['source_line'] = (
349 '(cherry picked from commit %s\n %s %s)' % (commit, url, branch))
Tzung-Bi Shih886c9092019-09-02 12:46:16 +0800350 if args['tag'] is None:
351 args['tag'] = 'FROMGIT: '
352
Stephen Boyd4b3869a2020-01-24 15:35:37 -0800353 if args['replace']:
354 _git(['reset', '--hard', 'HEAD~1'])
355
Alexandru M Stan725c71f2019-12-11 16:53:33 -0800356 return _git_returncode(['cherry-pick', commit])
Tzung-Bi Shih886c9092019-09-02 12:46:16 +0800357
Stephen Boyd96396032020-02-25 10:12:59 -0800358def _match_gitweb(match, args):
359 """Match location: https://repoURL/commit/?h=branch&id=HASH."""
360 remote = match.group(1)
361 branch = match.group(2)
362 commit = match.group(3)
363
364 if args['debug']:
365 print('_match_gitweb: remote=%s branch=%s commit=%s' %
366 (remote, branch, commit))
367
368 try:
369 _git(['fetch', remote, branch])
370 except subprocess.CalledProcessError:
371 errprint('Error: Branch not in %s' % remote)
372 sys.exit(1)
373
374 url = remote
375
376 if args['source_line'] is None:
377 commit = _git(['rev-parse', commit])
378 args['source_line'] = (
379 '(cherry picked from commit %s\n %s %s)' % (commit, url, branch))
380 if args['tag'] is None:
381 args['tag'] = 'FROMGIT: '
382
383 if args['replace']:
384 _git(['reset', '--hard', 'HEAD~1'])
385
386 return _git_returncode(['cherry-pick', commit])
387
Alexandru M Stanfb5b5ee2014-12-04 13:32:55 -0800388def main(args):
389 """This is the main entrypoint for fromupstream.
390
391 Args:
392 args: sys.argv[1:]
393
394 Returns:
395 An int return code.
396 """
397 parser = argparse.ArgumentParser()
398
399 parser.add_argument('--bug', '-b',
Guenter Roeckf47a50c2018-07-25 12:41:36 -0700400 type=str, help='BUG= line')
Alexandru M Stanfb5b5ee2014-12-04 13:32:55 -0800401 parser.add_argument('--test', '-t',
Guenter Roeckf47a50c2018-07-25 12:41:36 -0700402 type=str, help='TEST= line')
Stephen Boyd24b309b2018-11-06 22:11:00 -0800403 parser.add_argument('--crbug', action='append',
404 type=int, help='BUG=chromium: line')
405 parser.add_argument('--buganizer', action='append',
406 type=int, help='BUG=b: line')
Alexandru M Stanfb5b5ee2014-12-04 13:32:55 -0800407 parser.add_argument('--changeid', '-c',
408 help='Overrides the gerrit generated Change-Id line')
409
Tzung-Bi Shihf5d25a82019-09-02 11:40:09 +0800410 parser.add_argument('--replace', '-r',
Alexandru M Stanfb5b5ee2014-12-04 13:32:55 -0800411 action='store_true',
Tzung-Bi Shih196d31e2019-09-01 18:16:28 +0800412 help='Replaces the HEAD commit with this one, taking '
413 'its properties(BUG, TEST, Change-Id). Useful for '
Alexandru M Stanfb5b5ee2014-12-04 13:32:55 -0800414 'updating commits.')
415 parser.add_argument('--nosignoff',
416 dest='signoff', action='store_false')
Tzung-Bi Shih5100c742019-09-02 10:28:32 +0800417 parser.add_argument('--debug', '-d', action='store_true',
418 help='Prints more verbose logs.')
Alexandru M Stanfb5b5ee2014-12-04 13:32:55 -0800419
420 parser.add_argument('--tag',
421 help='Overrides the tag from the title')
422 parser.add_argument('--source', '-s',
423 dest='source_line', type=str,
Tzung-Bi Shih196d31e2019-09-01 18:16:28 +0800424 help='Overrides the source line, last line, ex: '
Alexandru M Stanfb5b5ee2014-12-04 13:32:55 -0800425 '(am from http://....)')
426 parser.add_argument('locations',
Douglas Andersonc77a8b82018-05-04 17:02:03 -0700427 nargs='+',
Tzung-Bi Shih196d31e2019-09-01 18:16:28 +0800428 help='Patchwork ID (pw://### or pw://PROJECT/###, '
429 'where PROJECT is defined in ~/.pwclientrc; if no '
430 'PROJECT is specified, the default is retrieved from '
431 '~/.pwclientrc), '
Stephen Boydb68c17a2019-09-26 15:08:02 -0700432 'Message-ID (msgid://MSGID), '
Tzung-Bi Shih196d31e2019-09-01 18:16:28 +0800433 'linux commit like linux://HASH, or '
434 'git reference like git://remote/branch/HASH or '
435 'git://repoURL#branch/HASH or '
Stephen Boyd96396032020-02-25 10:12:59 -0800436 'https://repoURL#branch/HASH or '
437 'https://repoURL/commit/?h=branch&id=HASH')
Alexandru M Stanfb5b5ee2014-12-04 13:32:55 -0800438
439 args = vars(parser.parse_args(args))
440
Stephen Boyd24b309b2018-11-06 22:11:00 -0800441 buglist = [args['bug']] if args['bug'] else []
442 if args['buganizer']:
443 buglist += ['b:{0}'.format(x) for x in args['buganizer']]
444 if args['crbug']:
445 buglist += ['chromium:{0}'.format(x) for x in args['crbug']]
Brian Norris667a0cb2018-12-07 09:28:46 -0800446 if buglist:
447 args['bug'] = ', '.join(buglist)
Stephen Boyd24b309b2018-11-06 22:11:00 -0800448
Alexandru M Stanfb5b5ee2014-12-04 13:32:55 -0800449 if args['replace']:
Alexandru M Stan725c71f2019-12-11 16:53:33 -0800450 old_commit_message = _git(['show', '-s', '--format=%B', 'HEAD'])
Tzung-Bi Shih231fada2019-09-02 00:54:59 +0800451
452 # It is possible that multiple Change-Ids are in the commit message
453 # (due to cherry picking). We only want to pull out the first one.
454 changeid_match = re.search('^Change-Id: (.*)$',
455 old_commit_message, re.MULTILINE)
456 if changeid_match:
457 args['changeid'] = changeid_match.group(1)
458
Tzung-Bi Shihe1e0e7d2019-09-02 15:04:38 +0800459 bugs = re.findall('^BUG=(.*)$', old_commit_message, re.MULTILINE)
Tzung-Bi Shih04345302019-09-02 12:04:01 +0800460 if args['bug'] is None and bugs:
461 args['bug'] = '\nBUG='.join(bugs)
462
Tzung-Bi Shihe1e0e7d2019-09-02 15:04:38 +0800463 tests = re.findall('^TEST=(.*)$', old_commit_message, re.MULTILINE)
Tzung-Bi Shih04345302019-09-02 12:04:01 +0800464 if args['test'] is None and tests:
465 args['test'] = '\nTEST='.join(tests)
Alexandru M Stanfb5b5ee2014-12-04 13:32:55 -0800466 # TODO: deal with multiline BUG/TEST better
Alexandru M Stanfb5b5ee2014-12-04 13:32:55 -0800467
Guenter Roeckf47a50c2018-07-25 12:41:36 -0700468 if args['bug'] is None or args['test'] is None:
Tzung-Bi Shih196d31e2019-09-01 18:16:28 +0800469 parser.error('BUG=/TEST= lines are required; --replace can help '
Stephen Boyde6fdf912018-11-09 10:30:57 -0800470 'automate, or set via --bug/--test')
Guenter Roeckf47a50c2018-07-25 12:41:36 -0700471
Tzung-Bi Shih5100c742019-09-02 10:28:32 +0800472 if args['debug']:
473 pprint.pprint(args)
474
Tzung-Bi Shih886c9092019-09-02 12:46:16 +0800475 re_matches = (
Tzung-Bi Shihe1e0e7d2019-09-02 15:04:38 +0800476 (re.compile(r'^pw://(([^/]+)/)?(\d+)'), _match_patchwork),
Stephen Boydb68c17a2019-09-26 15:08:02 -0700477 (re.compile(r'^msgid://<?([^>]*)>?'), _match_msgid),
Tzung-Bi Shihe1e0e7d2019-09-02 15:04:38 +0800478 (re.compile(r'^linux://([0-9a-f]+)'), _match_linux),
479 (re.compile(r'^(from)?git://([^/\#]+)/([^#]+)/([0-9a-f]+)$'),
Tzung-Bi Shih886c9092019-09-02 12:46:16 +0800480 _match_fromgit),
Tzung-Bi Shihe1e0e7d2019-09-02 15:04:38 +0800481 (re.compile(r'^((git|https)://.+)#(.+)/([0-9a-f]+)$'), _match_gitfetch),
Stephen Boyd96396032020-02-25 10:12:59 -0800482 (re.compile(r'^(https://.+)/commit/\?h=(.+)\&id=([0-9a-f]+)$'), _match_gitweb),
Tzung-Bi Shih886c9092019-09-02 12:46:16 +0800483 )
Alexandru M Stanfb5b5ee2014-12-04 13:32:55 -0800484
Tzung-Bi Shih886c9092019-09-02 12:46:16 +0800485 for location in args['locations']:
Tzung-Bi Shih5100c742019-09-02 10:28:32 +0800486 if args['debug']:
487 print('location=%s' % location)
488
Tzung-Bi Shih886c9092019-09-02 12:46:16 +0800489 for reg, handler in re_matches:
490 match = reg.match(location)
491 if match:
492 ret = handler(match, args)
493 break
Alexandru M Stanfb5b5ee2014-12-04 13:32:55 -0800494 else:
Tzung-Bi Shih436fdba2019-09-04 19:05:00 +0800495 errprint('Don\'t know what "%s" means.' % location)
Alexandru M Stanfb5b5ee2014-12-04 13:32:55 -0800496 sys.exit(1)
497
498 if ret != 0:
Guenter Roeckbdbb9cc2018-04-19 10:05:08 -0700499 conflicts = _get_conflicts()
Douglas Anderson2108e532018-04-30 09:50:42 -0700500 if args['tag'] == 'UPSTREAM: ':
501 args['tag'] = 'BACKPORT: '
502 else:
503 args['tag'] = 'BACKPORT: ' + args['tag']
Guenter Roeckbdbb9cc2018-04-19 10:05:08 -0700504 _pause_for_merge(conflicts)
505 else:
Douglas Andersonb6a10fe2019-08-12 13:53:30 -0700506 conflicts = ''
Alexandru M Stanfb5b5ee2014-12-04 13:32:55 -0800507
508 # extract commit message
Alexandru M Stan725c71f2019-12-11 16:53:33 -0800509 commit_message = _git(['show', '-s', '--format=%B', 'HEAD'])
Alexandru M Stanfb5b5ee2014-12-04 13:32:55 -0800510
Guenter Roeck2e4f2512018-04-24 09:20:51 -0700511 # Remove stray Change-Id, most likely from merge resolution
512 commit_message = re.sub(r'Change-Id:.*\n?', '', commit_message)
513
Brian Norris7a41b982018-06-01 10:28:29 -0700514 # Note the source location before tagging anything else
515 commit_message += '\n' + args['source_line']
516
Alexandru M Stanfb5b5ee2014-12-04 13:32:55 -0800517 # add automatic Change ID, BUG, and TEST (and maybe signoff too) so
518 # next commands know where to work on
519 commit_message += '\n'
Guenter Roeckbdbb9cc2018-04-19 10:05:08 -0700520 commit_message += conflicts
Alexandru M Stanfb5b5ee2014-12-04 13:32:55 -0800521 commit_message += '\n' + 'BUG=' + args['bug']
Harry Cuttsae372f32019-02-12 18:01:14 -0800522 commit_message += '\n' + _wrap_commit_line('TEST', args['test'])
Alexandru M Stanfb5b5ee2014-12-04 13:32:55 -0800523 if args['signoff']:
524 extra = ['-s']
525 else:
526 extra = []
Alexandru M Stan725c71f2019-12-11 16:53:33 -0800527 _git(['commit'] + extra + ['--amend', '-F', '-'], stdin=commit_message)
Alexandru M Stanfb5b5ee2014-12-04 13:32:55 -0800528
529 # re-extract commit message
Alexandru M Stan725c71f2019-12-11 16:53:33 -0800530 commit_message = _git(['show', '-s', '--format=%B', 'HEAD'])
Alexandru M Stanfb5b5ee2014-12-04 13:32:55 -0800531
Douglas Andersonbecd4e62019-09-25 13:40:55 -0700532 # If we see a "Link: " that seems to point to a Message-Id with an
533 # automatic Change-Id we'll snarf it out.
534 mo = re.search(r'^Link:.*(I[a-f0-9]{40})@changeid', commit_message,
535 re.MULTILINE)
536 if mo and args['changeid'] is None:
537 args['changeid'] = mo.group(1)
538
Alexandru M Stanfb5b5ee2014-12-04 13:32:55 -0800539 # replace changeid if needed
540 if args['changeid'] is not None:
541 commit_message = re.sub(r'(Change-Id: )(\w+)', r'\1%s' %
542 args['changeid'], commit_message)
543 args['changeid'] = None
544
545 # decorate it that it's from outside
546 commit_message = args['tag'] + commit_message
Alexandru M Stanfb5b5ee2014-12-04 13:32:55 -0800547
548 # commit everything
Alexandru M Stan725c71f2019-12-11 16:53:33 -0800549 _git(['commit', '--amend', '-F', '-'], stdin=commit_message)
Alexandru M Stanfb5b5ee2014-12-04 13:32:55 -0800550
Chirantan Ekbote4b08e712019-06-12 15:35:41 +0900551 return 0
Alexandru M Stanfb5b5ee2014-12-04 13:32:55 -0800552
553if __name__ == '__main__':
554 sys.exit(main(sys.argv[1:]))