blob: 3fd4a2860513c2ae56abeaa021137e403d555122 [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 Stan52c42df2021-02-02 17:17:20 -080010"""echo""" "This is a python script! Don't interpret it with bash."
11"""exit"""
12
Alexandru M Stanfb5b5ee2014-12-04 13:32:55 -080013import argparse
Ricardo Ribaldad1aaede2021-01-15 13:00:50 +010014from collections import OrderedDict
Alexandru M Stan725c71f2019-12-11 16:53:33 -080015import configparser
Tzung-Bi Shih436fdba2019-09-04 19:05:00 +080016import functools
Brian Norrisc3421042018-08-15 14:17:26 -070017import mailbox
Alexandru M Stanfb5b5ee2014-12-04 13:32:55 -080018import os
Tzung-Bi Shih5100c742019-09-02 10:28:32 +080019import pprint
Alexandru M Stanfb5b5ee2014-12-04 13:32:55 -080020import re
21import signal
Douglas Anderson3ef68772021-01-25 08:48:05 -080022import socket
Douglas Anderson297a3062020-09-09 12:47:09 -070023import ssl
Alexandru M Stanfb5b5ee2014-12-04 13:32:55 -080024import subprocess
25import sys
Harry Cuttsae372f32019-02-12 18:01:14 -080026import textwrap
Alexandru M Stan725c71f2019-12-11 16:53:33 -080027import urllib.request
28import xmlrpc.client
Alexandru M Stanfb5b5ee2014-12-04 13:32:55 -080029
Tzung-Bi Shih436fdba2019-09-04 19:05:00 +080030errprint = functools.partial(print, file=sys.stderr)
31
Brian Norris00148182020-08-20 10:46:51 -070032# pylint: disable=line-too-long
Abhishek Pandit-Subedi5ce64192020-11-02 16:10:17 -080033# Note: Do not include trailing / in any of these
Abhishek Pandit-Subediaea8c502020-07-09 21:56:12 -070034UPSTREAM_URLS = (
Douglas Andersoncebcefd2020-09-24 10:37:36 -070035 # Acceptable Linux URLs
Alexandru M Stanfb5b5ee2014-12-04 13:32:55 -080036 'git://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git',
37 'https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git',
38 'https://kernel.googlesource.com/pub/scm/linux/kernel/git/torvalds/linux.git',
Douglas Andersoncebcefd2020-09-24 10:37:36 -070039
40 # Acceptible Linux Firmware URLs
41 'git://git.kernel.org/pub/scm/linux/kernel/git/firmware/linux-firmware.git',
42 'https://git.kernel.org/pub/scm/linux/kernel/git/firmware/linux-firmware.git',
43 'https://kernel.googlesource.com/pub/scm/linux/kernel/git/firmware/linux-firmware.git',
44
45 # Upstream for various other projects
Brian Norris8043cfd2020-03-19 11:46:16 -070046 'git://w1.fi/srv/git/hostap.git',
Abhishek Pandit-Subediaea8c502020-07-09 21:56:12 -070047 'git://git.kernel.org/pub/scm/bluetooth/bluez.git',
Douglas Anderson482a6d62020-09-21 09:31:54 -070048 'https://github.com/andersson/qrtr.git',
Douglas Andersona3d1cb92021-02-01 09:13:07 -080049 'https://review.coreboot.org/flashrom.git'
Brian Norris8043cfd2020-03-19 11:46:16 -070050)
51
Stephen Boydb68c17a2019-09-26 15:08:02 -070052PATCHWORK_URLS = (
53 'https://lore.kernel.org/patchwork',
54 'https://patchwork.kernel.org',
55 'https://patchwork.ozlabs.org',
56 'https://patchwork.freedesktop.org',
Stephen Boydb68c17a2019-09-26 15:08:02 -070057)
58
Harry Cuttsae372f32019-02-12 18:01:14 -080059COMMIT_MESSAGE_WIDTH = 75
60
Brian Norris9f8a2be2018-06-01 11:14:08 -070061_PWCLIENTRC = os.path.expanduser('~/.pwclientrc')
62
Douglas Andersoncebcefd2020-09-24 10:37:36 -070063def _git(args, stdin=None, encoding='utf-8', no_stderr=False):
Alexandru M Stan725c71f2019-12-11 16:53:33 -080064 """Calls a git subcommand.
65
66 Similar to subprocess.check_output.
67
68 Args:
Brian Norris6baeb2e2020-03-18 12:13:30 -070069 args: subcommand + args passed to 'git'.
Alexandru M Stan725c71f2019-12-11 16:53:33 -080070 stdin: a string or bytes (depending on encoding) that will be passed
71 to the git subcommand.
72 encoding: either 'utf-8' (default) or None. Override it to None if
73 you want both stdin and stdout to be raw bytes.
Douglas Andersoncebcefd2020-09-24 10:37:36 -070074 no_stderr: If True, we'll eat stderr
Alexandru M Stan725c71f2019-12-11 16:53:33 -080075
76 Returns:
77 the stdout of the git subcommand, same type as stdin. The output is
78 also run through strip to make sure there's no extra whitespace.
79
80 Raises:
81 subprocess.CalledProcessError: when return code is not zero.
82 The exception has a .returncode attribute.
83 """
84 return subprocess.run(
85 ['git'] + args,
86 encoding=encoding,
87 input=stdin,
88 stdout=subprocess.PIPE,
Douglas Andersoncebcefd2020-09-24 10:37:36 -070089 stderr=(subprocess.PIPE if no_stderr else None),
Alexandru M Stan725c71f2019-12-11 16:53:33 -080090 check=True,
91 ).stdout.strip()
92
93def _git_returncode(*args, **kwargs):
94 """Same as _git, but return returncode instead of stdout.
95
96 Similar to subprocess.call.
97
98 Never raises subprocess.CalledProcessError.
99 """
100 try:
101 _git(*args, **kwargs)
102 return 0
103 except subprocess.CalledProcessError as e:
104 return e.returncode
105
Guenter Roeckbdbb9cc2018-04-19 10:05:08 -0700106def _get_conflicts():
107 """Report conflicting files."""
108 resolutions = ('DD', 'AU', 'UD', 'UA', 'DU', 'AA', 'UU')
109 conflicts = []
Alexandru M Stan725c71f2019-12-11 16:53:33 -0800110 output = _git(['status', '--porcelain', '--untracked-files=no'])
111 for line in output.splitlines():
Douglas Anderson46287f92018-04-30 09:58:24 -0700112 if not line:
Guenter Roeckbdbb9cc2018-04-19 10:05:08 -0700113 continue
Douglas Anderson46287f92018-04-30 09:58:24 -0700114 resolution, name = line.split(None, 1)
Guenter Roeckbdbb9cc2018-04-19 10:05:08 -0700115 if resolution in resolutions:
116 conflicts.append(' ' + name)
117 if not conflicts:
Douglas Andersonb6a10fe2019-08-12 13:53:30 -0700118 return ''
Guenter Roeckbdbb9cc2018-04-19 10:05:08 -0700119 return '\nConflicts:\n%s\n' % '\n'.join(conflicts)
120
Brian Norris8043cfd2020-03-19 11:46:16 -0700121def _find_upstream_remote(urls):
122 """Find a remote pointing to an upstream repository."""
Alexandru M Stan725c71f2019-12-11 16:53:33 -0800123 for remote in _git(['remote']).splitlines():
124 try:
Abhishek Pandit-Subedi5ce64192020-11-02 16:10:17 -0800125 if _git(['remote', 'get-url', remote]).rstrip('/') in urls:
Alexandru M Stan725c71f2019-12-11 16:53:33 -0800126 return remote
127 except subprocess.CalledProcessError:
128 # Kinda weird, get-url failing on an item that git just gave us.
129 continue
Guenter Roeckd66daa72018-04-19 10:31:25 -0700130 return None
131
Guenter Roeckbdbb9cc2018-04-19 10:05:08 -0700132def _pause_for_merge(conflicts):
Alexandru M Stanfb5b5ee2014-12-04 13:32:55 -0800133 """Pause and go in the background till user resolves the conflicts."""
134
Alexandru M Stan725c71f2019-12-11 16:53:33 -0800135 git_root = _git(['rev-parse', '--show-toplevel'])
Harry Cutts2bcd9af2020-02-20 16:27:50 -0800136 previous_head_hash = _git(['rev-parse', 'HEAD'])
Alexandru M Stanfb5b5ee2014-12-04 13:32:55 -0800137
138 paths = (
139 os.path.join(git_root, '.git', 'rebase-apply'),
140 os.path.join(git_root, '.git', 'CHERRY_PICK_HEAD'),
141 )
142 for path in paths:
143 if os.path.exists(path):
Tzung-Bi Shih436fdba2019-09-04 19:05:00 +0800144 errprint('Found "%s".' % path)
145 errprint(conflicts)
146 errprint('Please resolve the conflicts and restart the '
147 'shell job when done. Kill this job if you '
148 'aborted the conflict.')
Alexandru M Stanfb5b5ee2014-12-04 13:32:55 -0800149 os.kill(os.getpid(), signal.SIGTSTP)
Harry Cutts2bcd9af2020-02-20 16:27:50 -0800150
151 # Check the conflicts actually got resolved. Otherwise we'll end up
152 # modifying the wrong commit message and probably confusing people.
153 while previous_head_hash == _git(['rev-parse', 'HEAD']):
154 errprint('Error: no new commit has been made. Did you forget to run '
155 '`git am --continue` or `git cherry-pick --continue`?')
156 errprint('Please create a new commit and restart the shell job (or kill'
157 ' it if you aborted the conflict).')
158 os.kill(os.getpid(), signal.SIGTSTP)
Alexandru M Stanfb5b5ee2014-12-04 13:32:55 -0800159
Brian Norris9f8a2be2018-06-01 11:14:08 -0700160def _get_pw_url(project):
161 """Retrieve the patchwork server URL from .pwclientrc.
162
Mike Frysingerf80ca212018-07-13 15:02:52 -0400163 Args:
164 project: patchwork project name; if None, we retrieve the default
165 from pwclientrc
Brian Norris9f8a2be2018-06-01 11:14:08 -0700166 """
Alexandru M Stan725c71f2019-12-11 16:53:33 -0800167 config = configparser.ConfigParser()
Brian Norris9f8a2be2018-06-01 11:14:08 -0700168 config.read([_PWCLIENTRC])
169
170 if project is None:
171 try:
172 project = config.get('options', 'default')
Alexandru M Stan725c71f2019-12-11 16:53:33 -0800173 except (configparser.NoSectionError, configparser.NoOptionError) as e:
174 errprint('Error: no default patchwork project found in %s. (%r)'
175 % (_PWCLIENTRC, e))
Brian Norris9f8a2be2018-06-01 11:14:08 -0700176 sys.exit(1)
177
178 if not config.has_option(project, 'url'):
Tzung-Bi Shih436fdba2019-09-04 19:05:00 +0800179 errprint("Error: patchwork URL not found for project '%s'" % project)
Brian Norris9f8a2be2018-06-01 11:14:08 -0700180 sys.exit(1)
181
182 url = config.get(project, 'url')
Brian Norris2d4e9762018-08-15 13:11:47 -0700183 # Strip trailing 'xmlrpc' and/or trailing slash.
184 return re.sub('/(xmlrpc/)?$', '', url)
Brian Norris9f8a2be2018-06-01 11:14:08 -0700185
Harry Cuttsae372f32019-02-12 18:01:14 -0800186def _wrap_commit_line(prefix, content):
Tzung-Bi Shih2d061112020-08-17 10:27:24 +0800187 line = prefix + content
188 indent = ' ' * len(prefix)
Tzung-Bi Shihc4cfabb2020-08-10 12:57:23 +0800189
190 ret = textwrap.fill(line, COMMIT_MESSAGE_WIDTH, subsequent_indent=indent)
Tzung-Bi Shih2d061112020-08-17 10:27:24 +0800191 return ret[len(prefix):]
Harry Cuttsae372f32019-02-12 18:01:14 -0800192
Stephen Boydb68c17a2019-09-26 15:08:02 -0700193def _pick_patchwork(url, patch_id, args):
Tzung-Bi Shih886c9092019-09-02 12:46:16 +0800194 if args['tag'] is None:
195 args['tag'] = 'FROMLIST: '
196
Brian Norris8553f032020-03-18 11:59:02 -0700197 try:
Pi-Hsun Shih3a083842020-11-16 18:32:29 +0800198 opener = urllib.request.urlopen('%s/patch/%s/mbox' % (url, patch_id))
Brian Norris8553f032020-03-18 11:59:02 -0700199 except urllib.error.HTTPError as e:
200 errprint('Error: could not download patch: %s' % e)
Tzung-Bi Shih886c9092019-09-02 12:46:16 +0800201 sys.exit(1)
202 patch_contents = opener.read()
203
204 if not patch_contents:
Tzung-Bi Shih436fdba2019-09-04 19:05:00 +0800205 errprint('Error: No patch content found')
Tzung-Bi Shih886c9092019-09-02 12:46:16 +0800206 sys.exit(1)
207
Douglas Andersonbecd4e62019-09-25 13:40:55 -0700208 message_id = mailbox.Message(patch_contents)['Message-Id']
209 message_id = re.sub('^<|>$', '', message_id.strip())
Tzung-Bi Shih886c9092019-09-02 12:46:16 +0800210 if args['source_line'] is None:
Pi-Hsun Shih3a083842020-11-16 18:32:29 +0800211 args['source_line'] = '(am from %s/patch/%s/)' % (url, patch_id)
Brian Norris8553f032020-03-18 11:59:02 -0700212 for url_template in [
Brian Norris655b5ce2020-05-08 11:37:38 -0700213 'https://lore.kernel.org/r/%s',
Brian Norris8553f032020-03-18 11:59:02 -0700214 # hostap project (and others) are here, but not kernel.org.
215 'https://marc.info/?i=%s',
216 # public-inbox comes last as a "default"; it has a nice error page
217 # pointing to other redirectors, even if it doesn't have what
218 # you're looking for directly.
219 'https://public-inbox.org/git/%s',
220 ]:
221 alt_url = url_template % message_id
222 if args['debug']:
223 print('Probing archive for message at: %s' % alt_url)
224 try:
225 urllib.request.urlopen(alt_url)
226 except urllib.error.HTTPError as e:
227 # Skip all HTTP errors. We can expect 404 for archives that
228 # don't have this MessageId, or 300 for public-inbox ("not
229 # found, but try these other redirects"). It's less clear what
230 # to do with transitory (or is it permanent?) server failures.
231 if args['debug']:
232 print('Skipping URL %s, error: %s' % (alt_url, e))
233 continue
234 # Success!
235 if args['debug']:
236 print('Found at %s' % alt_url)
237 break
238 else:
239 errprint(
240 "WARNING: couldn't find working MessageId URL; "
241 'defaulting to "%s"' % alt_url)
242 args['source_line'] += '\n(also found at %s)' % alt_url
Tzung-Bi Shih886c9092019-09-02 12:46:16 +0800243
Douglas Andersonbecd4e62019-09-25 13:40:55 -0700244 # Auto-snarf the Change-Id if it was encoded into the Message-Id.
245 mo = re.match(r'.*(I[a-f0-9]{40})@changeid$', message_id)
246 if mo and args['changeid'] is None:
247 args['changeid'] = mo.group(1)
248
Tzung-Bi Shih886c9092019-09-02 12:46:16 +0800249 if args['replace']:
Alexandru M Stan725c71f2019-12-11 16:53:33 -0800250 _git(['reset', '--hard', 'HEAD~1'])
Tzung-Bi Shih886c9092019-09-02 12:46:16 +0800251
Douglas Andersona2e91c42020-08-21 08:46:09 -0700252 return _git_returncode(['am', '-3'], stdin=patch_contents, encoding=None)
Tzung-Bi Shih886c9092019-09-02 12:46:16 +0800253
Stephen Boydb68c17a2019-09-26 15:08:02 -0700254def _match_patchwork(match, args):
255 """Match location: pw://### or pw://PROJECT/###."""
256 pw_project = match.group(2)
Pi-Hsun Shih3a083842020-11-16 18:32:29 +0800257 patch_id = match.group(3)
Stephen Boydb68c17a2019-09-26 15:08:02 -0700258
259 if args['debug']:
Pi-Hsun Shih3a083842020-11-16 18:32:29 +0800260 print('_match_patchwork: pw_project=%s, patch_id=%s' %
Stephen Boydb68c17a2019-09-26 15:08:02 -0700261 (pw_project, patch_id))
262
263 url = _get_pw_url(pw_project)
264 return _pick_patchwork(url, patch_id, args)
265
266def _match_msgid(match, args):
267 """Match location: msgid://MSGID."""
268 msgid = match.group(1)
269
270 if args['debug']:
271 print('_match_msgid: message_id=%s' % (msgid))
272
273 # Patchwork requires the brackets so force it
274 msgid = '<' + msgid + '>'
275 url = None
276 for url in PATCHWORK_URLS:
Alexandru M Stan725c71f2019-12-11 16:53:33 -0800277 rpc = xmlrpc.client.ServerProxy(url + '/xmlrpc/')
Douglas Anderson297a3062020-09-09 12:47:09 -0700278 try:
279 res = rpc.patch_list({'msgid': msgid})
280 except ssl.SSLCertVerificationError:
281 errprint('Error: server "%s" gave an SSL error, skipping' % url)
282 continue
Douglas Anderson3ef68772021-01-25 08:48:05 -0800283 except socket.gaierror as e:
284 errprint('Error: server "%s" gave socket error "%s", skipping' % (url, e))
Stephen Boydb68c17a2019-09-26 15:08:02 -0700285 if res:
286 patch_id = res[0]['id']
287 break
288 else:
289 errprint('Error: could not find patch based on message id')
290 sys.exit(1)
291
292 return _pick_patchwork(url, patch_id, args)
293
Abhishek Pandit-Subediaea8c502020-07-09 21:56:12 -0700294def _upstream(commit, urls, args):
Tzung-Bi Shih886c9092019-09-02 12:46:16 +0800295 if args['debug']:
Abhishek Pandit-Subediaea8c502020-07-09 21:56:12 -0700296 print('_upstream: commit=%s' % commit)
Tzung-Bi Shih886c9092019-09-02 12:46:16 +0800297
Brian Norris8043cfd2020-03-19 11:46:16 -0700298 # Confirm an upstream remote is setup.
299 remote = _find_upstream_remote(urls)
300 if not remote:
Tzung-Bi Shih436fdba2019-09-04 19:05:00 +0800301 errprint('Error: need a valid upstream remote')
Tzung-Bi Shih886c9092019-09-02 12:46:16 +0800302 sys.exit(1)
303
Douglas Andersoncebcefd2020-09-24 10:37:36 -0700304 branches = ['main', 'master']
305 for branch in branches:
306 remote_ref = '%s/%s' % (remote, branch)
307 try:
308 _git(['merge-base', '--is-ancestor', commit, remote_ref],
309 no_stderr=True)
310 except subprocess.CalledProcessError:
311 continue
312 break
313 else:
314 errprint('Error: Commit not in %s, branches: %s' % (
315 remote, ', '.join(branches)))
Tzung-Bi Shih886c9092019-09-02 12:46:16 +0800316 sys.exit(1)
317
318 if args['source_line'] is None:
Alexandru M Stan725c71f2019-12-11 16:53:33 -0800319 commit = _git(['rev-parse', commit])
Tzung-Bi Shih886c9092019-09-02 12:46:16 +0800320 args['source_line'] = ('(cherry picked from commit %s)' %
321 (commit))
322 if args['tag'] is None:
323 args['tag'] = 'UPSTREAM: '
324
325 if args['replace']:
Alexandru M Stan725c71f2019-12-11 16:53:33 -0800326 _git(['reset', '--hard', 'HEAD~1'])
Tzung-Bi Shih886c9092019-09-02 12:46:16 +0800327
Alexandru M Stan725c71f2019-12-11 16:53:33 -0800328 return _git_returncode(['cherry-pick', commit])
Tzung-Bi Shih886c9092019-09-02 12:46:16 +0800329
Abhishek Pandit-Subediaea8c502020-07-09 21:56:12 -0700330def _match_upstream(match, args):
331 """Match location: linux://HASH and upstream://HASH."""
Brian Norris8043cfd2020-03-19 11:46:16 -0700332 commit = match.group(1)
Abhishek Pandit-Subediaea8c502020-07-09 21:56:12 -0700333 return _upstream(commit, urls=UPSTREAM_URLS, args=args)
Brian Norris8043cfd2020-03-19 11:46:16 -0700334
Tzung-Bi Shih886c9092019-09-02 12:46:16 +0800335def _match_fromgit(match, args):
336 """Match location: git://remote/branch/HASH."""
337 remote = match.group(2)
338 branch = match.group(3)
339 commit = match.group(4)
340
341 if args['debug']:
342 print('_match_fromgit: remote=%s branch=%s commit=%s' %
343 (remote, branch, commit))
344
Alexandru M Stan725c71f2019-12-11 16:53:33 -0800345 try:
346 _git(['merge-base', '--is-ancestor', commit,
347 '%s/%s' % (remote, branch)])
348 except subprocess.CalledProcessError:
Tzung-Bi Shih436fdba2019-09-04 19:05:00 +0800349 errprint('Error: Commit not in %s/%s' % (remote, branch))
Tzung-Bi Shih886c9092019-09-02 12:46:16 +0800350 sys.exit(1)
351
Alexandru M Stan725c71f2019-12-11 16:53:33 -0800352 url = _git(['remote', 'get-url', remote])
Tzung-Bi Shih886c9092019-09-02 12:46:16 +0800353
354 if args['source_line'] is None:
Alexandru M Stan725c71f2019-12-11 16:53:33 -0800355 commit = _git(['rev-parse', commit])
Tzung-Bi Shiha8f310d2019-09-04 19:10:10 +0800356 args['source_line'] = (
357 '(cherry picked from commit %s\n %s %s)' % (commit, url, branch))
Tzung-Bi Shih886c9092019-09-02 12:46:16 +0800358 if args['tag'] is None:
359 args['tag'] = 'FROMGIT: '
360
361 if args['replace']:
Alexandru M Stan725c71f2019-12-11 16:53:33 -0800362 _git(['reset', '--hard', 'HEAD~1'])
Tzung-Bi Shih886c9092019-09-02 12:46:16 +0800363
Alexandru M Stan725c71f2019-12-11 16:53:33 -0800364 return _git_returncode(['cherry-pick', commit])
Tzung-Bi Shih886c9092019-09-02 12:46:16 +0800365
366def _match_gitfetch(match, args):
367 """Match location: (git|https)://repoURL#branch/HASH."""
368 remote = match.group(1)
369 branch = match.group(3)
370 commit = match.group(4)
371
372 if args['debug']:
373 print('_match_gitfetch: remote=%s branch=%s commit=%s' %
374 (remote, branch, commit))
375
Alexandru M Stan725c71f2019-12-11 16:53:33 -0800376 try:
377 _git(['fetch', remote, branch])
378 except subprocess.CalledProcessError:
Tzung-Bi Shih436fdba2019-09-04 19:05:00 +0800379 errprint('Error: Branch not in %s' % remote)
Tzung-Bi Shih886c9092019-09-02 12:46:16 +0800380 sys.exit(1)
381
382 url = remote
383
384 if args['source_line'] is None:
Alexandru M Stan725c71f2019-12-11 16:53:33 -0800385 commit = _git(['rev-parse', commit])
Tzung-Bi Shiha8f310d2019-09-04 19:10:10 +0800386 args['source_line'] = (
387 '(cherry picked from commit %s\n %s %s)' % (commit, url, branch))
Tzung-Bi Shih886c9092019-09-02 12:46:16 +0800388 if args['tag'] is None:
389 args['tag'] = 'FROMGIT: '
390
Stephen Boyd4b3869a2020-01-24 15:35:37 -0800391 if args['replace']:
392 _git(['reset', '--hard', 'HEAD~1'])
393
Alexandru M Stan725c71f2019-12-11 16:53:33 -0800394 return _git_returncode(['cherry-pick', commit])
Tzung-Bi Shih886c9092019-09-02 12:46:16 +0800395
Stephen Boyd96396032020-02-25 10:12:59 -0800396def _match_gitweb(match, args):
397 """Match location: https://repoURL/commit/?h=branch&id=HASH."""
398 remote = match.group(1)
399 branch = match.group(2)
400 commit = match.group(3)
401
402 if args['debug']:
403 print('_match_gitweb: remote=%s branch=%s commit=%s' %
404 (remote, branch, commit))
405
406 try:
407 _git(['fetch', remote, branch])
408 except subprocess.CalledProcessError:
409 errprint('Error: Branch not in %s' % remote)
410 sys.exit(1)
411
412 url = remote
413
414 if args['source_line'] is None:
415 commit = _git(['rev-parse', commit])
416 args['source_line'] = (
417 '(cherry picked from commit %s\n %s %s)' % (commit, url, branch))
418 if args['tag'] is None:
419 args['tag'] = 'FROMGIT: '
420
421 if args['replace']:
422 _git(['reset', '--hard', 'HEAD~1'])
423
424 return _git_returncode(['cherry-pick', commit])
425
Ricardo Ribaldad1aaede2021-01-15 13:00:50 +0100426def _remove_dup_bugs(bugs):
427 """Remove the duplicated bugs from a string keeping the original order."""
428
429 # Standardize all the spacing around bugs
430 bugs = re.sub(r'\s*,\s*', ', ', bugs)
431
432 # Create a list of bugs
433 bugs = bugs.split(', ')
434
435 # Remove duplicates keeping order
436 bugs = list(OrderedDict.fromkeys(bugs).keys())
437
438 # Convert into a string again
439 bugs = ', '.join(bugs)
440
441 return bugs
442
Alexandru M Stanfb5b5ee2014-12-04 13:32:55 -0800443def main(args):
444 """This is the main entrypoint for fromupstream.
445
446 Args:
447 args: sys.argv[1:]
448
449 Returns:
450 An int return code.
451 """
452 parser = argparse.ArgumentParser()
453
Ricardo Ribaldab7ed04a2021-01-14 17:12:59 +0000454 parser.add_argument('--bug', '-b', action='append', default=[],
Guenter Roeckf47a50c2018-07-25 12:41:36 -0700455 type=str, help='BUG= line')
Brian Norris6bcfa392020-08-20 10:38:05 -0700456 parser.add_argument('--test', '-t', action='append', default=[],
Guenter Roeckf47a50c2018-07-25 12:41:36 -0700457 type=str, help='TEST= line')
Ricardo Ribaldab7ed04a2021-01-14 17:12:59 +0000458 parser.add_argument('--crbug', action='append', default=[],
Stephen Boyd24b309b2018-11-06 22:11:00 -0800459 type=int, help='BUG=chromium: line')
Ricardo Ribaldab7ed04a2021-01-14 17:12:59 +0000460 parser.add_argument('--buganizer', action='append', default=[],
Stephen Boyd24b309b2018-11-06 22:11:00 -0800461 type=int, help='BUG=b: line')
Alexandru M Stanfb5b5ee2014-12-04 13:32:55 -0800462 parser.add_argument('--changeid', '-c',
463 help='Overrides the gerrit generated Change-Id line')
Tzung-Bi Shih1a262c02020-08-13 10:49:55 +0800464 parser.add_argument('--cqdepend',
465 type=str, help='Cq-Depend: line')
Alexandru M Stanfb5b5ee2014-12-04 13:32:55 -0800466
Tzung-Bi Shihf5d25a82019-09-02 11:40:09 +0800467 parser.add_argument('--replace', '-r',
Alexandru M Stanfb5b5ee2014-12-04 13:32:55 -0800468 action='store_true',
Tzung-Bi Shih196d31e2019-09-01 18:16:28 +0800469 help='Replaces the HEAD commit with this one, taking '
470 'its properties(BUG, TEST, Change-Id). Useful for '
Alexandru M Stanfb5b5ee2014-12-04 13:32:55 -0800471 'updating commits.')
472 parser.add_argument('--nosignoff',
473 dest='signoff', action='store_false')
Tzung-Bi Shih5100c742019-09-02 10:28:32 +0800474 parser.add_argument('--debug', '-d', action='store_true',
475 help='Prints more verbose logs.')
Alexandru M Stanfb5b5ee2014-12-04 13:32:55 -0800476
477 parser.add_argument('--tag',
478 help='Overrides the tag from the title')
479 parser.add_argument('--source', '-s',
480 dest='source_line', type=str,
Tzung-Bi Shih196d31e2019-09-01 18:16:28 +0800481 help='Overrides the source line, last line, ex: '
Alexandru M Stanfb5b5ee2014-12-04 13:32:55 -0800482 '(am from http://....)')
483 parser.add_argument('locations',
Douglas Andersonc77a8b82018-05-04 17:02:03 -0700484 nargs='+',
Tzung-Bi Shih196d31e2019-09-01 18:16:28 +0800485 help='Patchwork ID (pw://### or pw://PROJECT/###, '
486 'where PROJECT is defined in ~/.pwclientrc; if no '
487 'PROJECT is specified, the default is retrieved from '
488 '~/.pwclientrc), '
Stephen Boydb68c17a2019-09-26 15:08:02 -0700489 'Message-ID (msgid://MSGID), '
Brian Norris8043cfd2020-03-19 11:46:16 -0700490 'linux commit like linux://HASH, '
Abhishek Pandit-Subediaea8c502020-07-09 21:56:12 -0700491 'upstream commit like upstream://HASH, or '
Tzung-Bi Shih196d31e2019-09-01 18:16:28 +0800492 'git reference like git://remote/branch/HASH or '
493 'git://repoURL#branch/HASH or '
Stephen Boyd96396032020-02-25 10:12:59 -0800494 'https://repoURL#branch/HASH or '
495 'https://repoURL/commit/?h=branch&id=HASH')
Alexandru M Stanfb5b5ee2014-12-04 13:32:55 -0800496
497 args = vars(parser.parse_args(args))
498
Tzung-Bi Shih621fed02020-08-14 22:58:55 +0800499 cq_depends = [args['cqdepend']] if args['cqdepend'] else []
500
Ricardo Ribaldab7ed04a2021-01-14 17:12:59 +0000501 bugs = args['bug']
502 bugs += ['b:%d' % x for x in args['buganizer']]
503 bugs += ['chromium:%d' % x for x in args['crbug']]
504 bugs = ', '.join(bugs)
Ricardo Ribaldad1aaede2021-01-15 13:00:50 +0100505 bugs = _remove_dup_bugs(bugs)
Ricardo Ribaldab7ed04a2021-01-14 17:12:59 +0000506 bug_lines = [x.strip(' ,') for x in
507 _wrap_commit_line('BUG=', bugs).split('\n')]
Stephen Boyd24b309b2018-11-06 22:11:00 -0800508
Brian Norris6bcfa392020-08-20 10:38:05 -0700509 test_lines = [_wrap_commit_line('TEST=', x) for x in args['test']]
Tzung-Bi Shihc4cfabb2020-08-10 12:57:23 +0800510
Alexandru M Stanfb5b5ee2014-12-04 13:32:55 -0800511 if args['replace']:
Alexandru M Stan725c71f2019-12-11 16:53:33 -0800512 old_commit_message = _git(['show', '-s', '--format=%B', 'HEAD'])
Tzung-Bi Shih231fada2019-09-02 00:54:59 +0800513
514 # It is possible that multiple Change-Ids are in the commit message
515 # (due to cherry picking). We only want to pull out the first one.
516 changeid_match = re.search('^Change-Id: (.*)$',
517 old_commit_message, re.MULTILINE)
Tzung-Bi Shih5fd0fd52020-08-13 11:06:33 +0800518 if args['changeid'] is None and changeid_match:
Tzung-Bi Shih231fada2019-09-02 00:54:59 +0800519 args['changeid'] = changeid_match.group(1)
520
Tzung-Bi Shih621fed02020-08-14 22:58:55 +0800521 if not cq_depends:
522 cq_depends = re.findall(r'^Cq-Depend:\s+(.*)$',
523 old_commit_message, re.MULTILINE)
Tzung-Bi Shihdfe82002020-08-13 11:00:56 +0800524
Tzung-Bi Shih621fed02020-08-14 22:58:55 +0800525 if not bug_lines:
526 bug_lines = re.findall(r'^BUG=(.*)$',
527 old_commit_message, re.MULTILINE)
Tzung-Bi Shih04345302019-09-02 12:04:01 +0800528
Tzung-Bi Shih621fed02020-08-14 22:58:55 +0800529 if not test_lines:
530 # Note: use (?=...) to avoid to consume the source string
531 test_lines = re.findall(r"""
532 ^TEST=(.*?) # Match start from TEST= until
533 \n # (to remove the tailing newlines)
534 (?=^$| # a blank line
535 ^Cq-Depend:| # or Cq-Depend:
536 ^Change-Id:| # or Change-Id:
537 ^BUG=| # or following BUG=
538 ^TEST=) # or another TEST=
539 """,
540 old_commit_message, re.MULTILINE | re.DOTALL | re.VERBOSE)
Alexandru M Stanfb5b5ee2014-12-04 13:32:55 -0800541
Tzung-Bi Shih621fed02020-08-14 22:58:55 +0800542 if not bug_lines or not test_lines:
Tzung-Bi Shih196d31e2019-09-01 18:16:28 +0800543 parser.error('BUG=/TEST= lines are required; --replace can help '
Stephen Boyde6fdf912018-11-09 10:30:57 -0800544 'automate, or set via --bug/--test')
Guenter Roeckf47a50c2018-07-25 12:41:36 -0700545
Tzung-Bi Shih5100c742019-09-02 10:28:32 +0800546 if args['debug']:
547 pprint.pprint(args)
548
Tzung-Bi Shih886c9092019-09-02 12:46:16 +0800549 re_matches = (
Pi-Hsun Shih3a083842020-11-16 18:32:29 +0800550 (re.compile(r'^pw://(([^/]+)/)?(.+)'), _match_patchwork),
Stephen Boydb68c17a2019-09-26 15:08:02 -0700551 (re.compile(r'^msgid://<?([^>]*)>?'), _match_msgid),
Abhishek Pandit-Subediaea8c502020-07-09 21:56:12 -0700552 (re.compile(r'^linux://([0-9a-f]+)'), _match_upstream),
553 (re.compile(r'^upstream://([0-9a-f]+)'), _match_upstream),
Tzung-Bi Shihe1e0e7d2019-09-02 15:04:38 +0800554 (re.compile(r'^(from)?git://([^/\#]+)/([^#]+)/([0-9a-f]+)$'),
Tzung-Bi Shih886c9092019-09-02 12:46:16 +0800555 _match_fromgit),
Tzung-Bi Shihe1e0e7d2019-09-02 15:04:38 +0800556 (re.compile(r'^((git|https)://.+)#(.+)/([0-9a-f]+)$'), _match_gitfetch),
Stephen Boyd96396032020-02-25 10:12:59 -0800557 (re.compile(r'^(https://.+)/commit/\?h=(.+)\&id=([0-9a-f]+)$'), _match_gitweb),
Tzung-Bi Shih886c9092019-09-02 12:46:16 +0800558 )
Alexandru M Stanfb5b5ee2014-12-04 13:32:55 -0800559
Ricardo Ribaldaf1348682020-11-03 13:51:07 +0100560 # Backup user provided parameters
561 user_source_line = args['source_line']
562 user_tag = args['tag']
563 user_changeid = args['changeid']
564
Tzung-Bi Shih886c9092019-09-02 12:46:16 +0800565 for location in args['locations']:
Ricardo Ribaldaf1348682020-11-03 13:51:07 +0100566 # Restore user parameters
567 args['source_line'] = user_source_line
568 args['tag'] = user_tag
569 args['changeid'] = user_changeid
570
Tzung-Bi Shih5100c742019-09-02 10:28:32 +0800571 if args['debug']:
572 print('location=%s' % location)
573
Tzung-Bi Shih886c9092019-09-02 12:46:16 +0800574 for reg, handler in re_matches:
575 match = reg.match(location)
576 if match:
577 ret = handler(match, args)
578 break
Alexandru M Stanfb5b5ee2014-12-04 13:32:55 -0800579 else:
Tzung-Bi Shih436fdba2019-09-04 19:05:00 +0800580 errprint('Don\'t know what "%s" means.' % location)
Alexandru M Stanfb5b5ee2014-12-04 13:32:55 -0800581 sys.exit(1)
582
583 if ret != 0:
Guenter Roeckbdbb9cc2018-04-19 10:05:08 -0700584 conflicts = _get_conflicts()
Douglas Anderson2108e532018-04-30 09:50:42 -0700585 if args['tag'] == 'UPSTREAM: ':
586 args['tag'] = 'BACKPORT: '
587 else:
588 args['tag'] = 'BACKPORT: ' + args['tag']
Guenter Roeckbdbb9cc2018-04-19 10:05:08 -0700589 _pause_for_merge(conflicts)
590 else:
Douglas Andersonb6a10fe2019-08-12 13:53:30 -0700591 conflicts = ''
Alexandru M Stanfb5b5ee2014-12-04 13:32:55 -0800592
593 # extract commit message
Alexandru M Stan725c71f2019-12-11 16:53:33 -0800594 commit_message = _git(['show', '-s', '--format=%B', 'HEAD'])
Alexandru M Stanfb5b5ee2014-12-04 13:32:55 -0800595
Guenter Roeck2e4f2512018-04-24 09:20:51 -0700596 # Remove stray Change-Id, most likely from merge resolution
597 commit_message = re.sub(r'Change-Id:.*\n?', '', commit_message)
598
Brian Norris7a41b982018-06-01 10:28:29 -0700599 # Note the source location before tagging anything else
600 commit_message += '\n' + args['source_line']
601
Alexandru M Stanfb5b5ee2014-12-04 13:32:55 -0800602 # add automatic Change ID, BUG, and TEST (and maybe signoff too) so
603 # next commands know where to work on
604 commit_message += '\n'
Guenter Roeckbdbb9cc2018-04-19 10:05:08 -0700605 commit_message += conflicts
Tzung-Bi Shih621fed02020-08-14 22:58:55 +0800606 commit_message += '\n'
607 commit_message += '\n'.join('BUG=%s' % bug for bug in bug_lines)
608 commit_message += '\n'
609 commit_message += '\n'.join('TEST=%s' % t for t in test_lines)
Brian Norris674209e2020-04-22 15:33:53 -0700610
611 extra = []
Alexandru M Stanfb5b5ee2014-12-04 13:32:55 -0800612 if args['signoff']:
Brian Norris674209e2020-04-22 15:33:53 -0700613 signoff = 'Signed-off-by: %s <%s>' % (
614 _git(['config', 'user.name']),
615 _git(['config', 'user.email']))
616 if not signoff in commit_message.splitlines():
617 extra += ['-s']
Alexandru M Stan725c71f2019-12-11 16:53:33 -0800618 _git(['commit'] + extra + ['--amend', '-F', '-'], stdin=commit_message)
Alexandru M Stanfb5b5ee2014-12-04 13:32:55 -0800619
620 # re-extract commit message
Alexandru M Stan725c71f2019-12-11 16:53:33 -0800621 commit_message = _git(['show', '-s', '--format=%B', 'HEAD'])
Alexandru M Stanfb5b5ee2014-12-04 13:32:55 -0800622
Douglas Andersonbecd4e62019-09-25 13:40:55 -0700623 # If we see a "Link: " that seems to point to a Message-Id with an
624 # automatic Change-Id we'll snarf it out.
625 mo = re.search(r'^Link:.*(I[a-f0-9]{40})@changeid', commit_message,
626 re.MULTILINE)
627 if mo and args['changeid'] is None:
628 args['changeid'] = mo.group(1)
629
Alexandru M Stanfb5b5ee2014-12-04 13:32:55 -0800630 # replace changeid if needed
631 if args['changeid'] is not None:
632 commit_message = re.sub(r'(Change-Id: )(\w+)', r'\1%s' %
633 args['changeid'], commit_message)
634 args['changeid'] = None
635
Tzung-Bi Shih621fed02020-08-14 22:58:55 +0800636 if cq_depends:
Tzung-Bi Shih1a262c02020-08-13 10:49:55 +0800637 commit_message = re.sub(
Tzung-Bi Shih621fed02020-08-14 22:58:55 +0800638 r'(Change-Id: \w+)',
639 r'%s\n\1' % '\n'.join('Cq-Depend: %s' % c for c in cq_depends),
Tzung-Bi Shih1a262c02020-08-13 10:49:55 +0800640 commit_message)
641
Alexandru M Stanfb5b5ee2014-12-04 13:32:55 -0800642 # decorate it that it's from outside
643 commit_message = args['tag'] + commit_message
Alexandru M Stanfb5b5ee2014-12-04 13:32:55 -0800644
645 # commit everything
Alexandru M Stan725c71f2019-12-11 16:53:33 -0800646 _git(['commit', '--amend', '-F', '-'], stdin=commit_message)
Alexandru M Stanfb5b5ee2014-12-04 13:32:55 -0800647
Chirantan Ekbote4b08e712019-06-12 15:35:41 +0900648 return 0
Alexandru M Stanfb5b5ee2014-12-04 13:32:55 -0800649
650if __name__ == '__main__':
651 sys.exit(main(sys.argv[1:]))