blob: 5b5f0513e4e2997e242ee2500fb971c5b51db681 [file] [log] [blame]
Hirthanan Subenderanb1866552020-03-20 14:01:14 -07001#!/usr/bin/env python3
2# -*- coding: utf-8 -*-"
3#
4# Copyright 2020 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.
7
8"""Module containing methods interfacing with git
9
10i.e Parsing git logs for change-id, full commit sha's, etc.
11"""
12
13from __future__ import print_function
Guenter Roeck85c10bb2020-04-16 15:36:47 -070014import logging
Hirthanan Subenderanb1866552020-03-20 14:01:14 -070015import os
16import re
17import subprocess
18import common
19
20
Hirthanan Subenderana90d8f82020-04-17 15:47:05 -070021def checkout_and_clean(kernel_path, branch):
22 """Cleanup uncommitted files in branch and checkout to be up to date with origin."""
23 reset_head = ['git', '-C', kernel_path, 'reset', '-q', '--hard', 'HEAD']
24 clean_untracked = ['git', '-C', kernel_path, 'clean', '-d', '-x', '-f', '-q']
25 checkout = ['git', '-C', kernel_path, 'checkout', '-q', branch]
26 reset_origin = ['git', '-C', kernel_path, 'reset', '-q', '--hard', 'origin/%s' % branch]
27 subprocess.run(reset_head, check=True)
28 subprocess.run(clean_untracked, check=True)
29 subprocess.run(checkout, check=True)
30 subprocess.run(reset_origin, check=True)
Hirthanan Subenderan7a81bed2020-04-16 17:44:31 -070031
32
Hirthanan Subenderanb1866552020-03-20 14:01:14 -070033def get_upstream_fullsha(abbrev_sha):
34 """Returns the full upstream sha for an abbreviated 12 digit sha using git cli"""
35 upstream_absolute_path = common.get_kernel_absolute_path(common.UPSTREAM_PATH)
36 try:
37 cmd = ['git', '-C', upstream_absolute_path, 'rev-parse', abbrev_sha]
Hirthanan Subenderanaffcc4a2020-03-25 14:42:33 -070038 full_sha = subprocess.check_output(cmd, encoding='utf-8')
Hirthanan Subenderanb1866552020-03-20 14:01:14 -070039 return full_sha.rstrip()
40 except subprocess.CalledProcessError as e:
41 raise type(e)('Could not find full upstream sha for %s' % abbrev_sha, e.cmd) from e
42
43
Hirthanan Subenderand6922c32020-03-23 14:17:40 -070044def get_commit_message(kernel_path, sha):
45 """Returns the commit message for a sha in a given local path to kernel."""
Hirthanan Subenderanb1866552020-03-20 14:01:14 -070046 try:
Hirthanan Subenderand6922c32020-03-23 14:17:40 -070047 cmd = ['git', '-C', kernel_path, 'log',
48 '--format=%B', '-n', '1', sha]
Hirthanan Subenderanaffcc4a2020-03-25 14:42:33 -070049 commit_message = subprocess.check_output(cmd, encoding='utf-8', errors='ignore')
Hirthanan Subenderan3450f512020-04-09 22:36:50 -070050
51 # Single newline following commit message
52 return commit_message.rstrip() + '\n'
Hirthanan Subenderanb1866552020-03-20 14:01:14 -070053 except subprocess.CalledProcessError as e:
Hirthanan Subenderand6922c32020-03-23 14:17:40 -070054 raise type(e)('Couldnt retrieve commit in kernel path %s for sha %s'
55 % (kernel_path, sha), e.cmd) from e
56
57
58def get_upstream_commit_message(upstream_sha):
59 """Returns the commit message for a given upstream sha using git cli."""
60 upstream_absolute_path = common.get_kernel_absolute_path(common.UPSTREAM_PATH)
61 return get_commit_message(upstream_absolute_path, upstream_sha)
62
63
64def get_chrome_commit_message(chrome_sha):
65 """Returns the commit message for a given chrome sha using git cli."""
66 chrome_absolute_path = common.get_kernel_absolute_path(common.CHROMEOS_PATH)
67 return get_commit_message(chrome_absolute_path, chrome_sha)
Hirthanan Subenderanb1866552020-03-20 14:01:14 -070068
69
Guenter Roeckbfda1242020-12-29 15:24:18 -080070def get_merge_sha(branch, sha):
71 """Returns SHA of merge commit for provided SHA if available"""
72
73 chrome_absolute_path = common.get_kernel_absolute_path(common.CHROMEOS_PATH)
74
75 try:
76 # Get list of merges in <branch> since <sha>
77 cmd = ['git', '-C', chrome_absolute_path, 'log', '--format=%h', '--abbrev=12',
78 '--ancestry-path', '--merges', '%s..%s' % (sha, branch)]
79 sha_list = subprocess.check_output(cmd, encoding='utf-8', errors='ignore',
80 stderr=subprocess.DEVNULL)
81 if not sha_list:
82 logging.info('No merge commit for sha %s in branch %s', sha, branch)
83 return None
84 # merge_sha is our presumed merge commit
85 merge_sha = sha_list.splitlines()[-1]
86 # Verify if <sha> is indeed part of the merge
87 cmd = ['git', '-C', chrome_absolute_path, 'log', '--format=%h', '--abbrev=12',
88 '%s~1..%s' % (merge_sha, merge_sha)]
89 sha_list = subprocess.check_output(cmd, encoding='utf-8', errors='ignore',
90 stderr=subprocess.DEVNULL)
91 if sha_list and sha in sha_list.splitlines():
92 return merge_sha
93 logging.info('Merge commit for sha %s found as %s, but sha is missing in merge',
94 sha, merge_sha)
95
96 except subprocess.CalledProcessError as e:
97 logging.info('Error "%s" while trying to find merge commit for sha %s in branch %s',
98 e, sha, branch)
99
100 return None
101
102
Hirthanan Subenderanb1866552020-03-20 14:01:14 -0700103def get_commit_changeid_linux_chrome(kernel_sha):
Hirthanan Subenderand6922c32020-03-23 14:17:40 -0700104 """Returns the changeid of the kernel_sha commit by parsing linux_chrome git log.
105
106 kernel_sha will be one of linux_stable or linux_chrome commits.
107 """
Hirthanan Subenderanb1866552020-03-20 14:01:14 -0700108 chrome_absolute_path = common.get_kernel_absolute_path(common.CHROMEOS_PATH)
109 try:
110 cmd = ['git', '-C', chrome_absolute_path, 'log', '--format=%B', '-n', '1', kernel_sha]
Hirthanan Subenderanaffcc4a2020-03-25 14:42:33 -0700111 commit_message = subprocess.check_output(cmd, encoding='utf-8', errors='ignore')
Hirthanan Subenderanb1866552020-03-20 14:01:14 -0700112
113 m = re.findall('^Change-Id: (I[a-z0-9]{40})$', commit_message, re.M)
114
115 # Get last change-id in case chrome sha cherry-picked/reverted into new commit
116 return m[-1]
117 except subprocess.CalledProcessError as e:
Hirthanan Subenderand6922c32020-03-23 14:17:40 -0700118 raise type(e)('Couldnt retrieve changeid for commit %s' % kernel_sha, e.cmd) from e
Hirthanan Subenderanb1866552020-03-20 14:01:14 -0700119 except IndexError as e:
Hirthanan Subenderand6922c32020-03-23 14:17:40 -0700120 # linux_stable kernel_sha's do not have an associated ChangeID
121 return None
Hirthanan Subenderanb1866552020-03-20 14:01:14 -0700122
123
Guenter Roeck949d05b2020-05-12 12:35:36 -0700124def get_tag_emails_linux_chrome(sha):
125 """Returns unique list of chromium.org or google.com e-mails.
126
127 The returned lust of e-mails is associated with tags found after
128 the last 'cherry picked from commit' message in the commit identified
129 by sha. Tags and e-mails are found by parsing the commit log.
130
131 sha is expected to be be a commit in linux_stable or in linux_chrome.
132 """
133 absolute_path = common.get_kernel_absolute_path(common.CHROMEOS_PATH)
134 try:
135 cmd = ['git', '-C', absolute_path, 'log', '--format=%B', '-n', '1', sha]
136 commit_message = subprocess.check_output(cmd, encoding='utf-8', errors='ignore')
137 # If the commit has been cherry-picked, use subsequent tags to create
138 # list of reviewers. Otherwise, use all tags. Either case, only return
139 # e-mail addresses from Google domains.
140 s = commit_message.split('cherry picked from commit')
141 tags = 'Signed-off-by|Reviewed-by|Tested-by|Commit-Queue'
142 domains = 'chromium.org|google.com'
143 m = '^(?:%s): .* <(.*@(?:%s))>$' % (tags, domains)
144 emails = re.findall(m, s[-1], re.M)
145 if not emails:
146 # Final fallback: In some situations, "cherry picked from"
147 # is at the very end of the commit description, with no
148 # subsequent tags. If that happens, look for tags in the
149 # entire description.
150 emails = re.findall(m, commit_message, re.M)
151 return list(set(emails))
152 except subprocess.CalledProcessError as e:
153 raise type(e)('Could not retrieve tag e-mails for commit %s' % sha, e.cmd) from e
154 except IndexError:
155 # sha does do not have a recognized tag
156 return None
157
158
Hirthanan Subenderanbbafac42020-04-10 16:18:16 -0700159def get_git_push_cmd(chromeos_branch, reviewers):
160 """Generates git push command with added reviewers and autogenerated tag.
161
162 Read more about gerrit tags here:
163 https://gerrit-review.googlesource.com/Documentation/cmd-receive-pack.html
164 """
165 git_push_head = 'git push origin HEAD:refs/for/%s' % chromeos_branch
166 reviewers_tag = ['r=%s'% r for r in reviewers]
167 autogenerated_tag = ['t=autogenerated']
168 tags = ','.join(reviewers_tag + autogenerated_tag)
169 return git_push_head + '%' + tags
170
171
Guenter Roecka3182e22020-07-02 11:31:26 -0700172def cherry_pick_and_push_fix(fixer_upstream_sha, fixer_changeid, chromeos_branch,
Hirthanan Subenderanbbafac42020-04-10 16:18:16 -0700173 fix_commit_message, reviewers):
174 """Cherry picks upstream commit into chrome repo.
175
176 Adds reviewers and autogenerated tag with the pushed commit.
177 """
Hirthanan Subenderanb1866552020-03-20 14:01:14 -0700178 cwd = os.getcwd()
179 chrome_absolute_path = common.get_kernel_absolute_path(common.CHROMEOS_PATH)
180
181 # reset linux_chrome repo to remove local changes
182 try:
183 os.chdir(chrome_absolute_path)
Hirthanan Subenderana90d8f82020-04-17 15:47:05 -0700184 checkout_and_clean(chrome_absolute_path, chromeos_branch)
Hirthanan Subenderanbbafac42020-04-10 16:18:16 -0700185 subprocess.run(['git', 'cherry-pick', '-n', fixer_upstream_sha], check=True)
186 subprocess.run(['git', 'commit', '-s', '-m', fix_commit_message], check=True)
Hirthanan Subenderanb1866552020-03-20 14:01:14 -0700187
188 # commit has been cherry-picked and committed locally, precommit hook
Guenter Roecka3182e22020-07-02 11:31:26 -0700189 # in git repository adds changeid to the commit message. Pick it unless
190 # we already have one passed as parameter.
191 if not fixer_changeid:
192 fixer_changeid = get_commit_changeid_linux_chrome('HEAD')
Guenter Roeckf82d0182020-06-19 07:50:01 -0700193
194 # Sometimes the commit hook doesn't attach the Change-Id to the last
195 # paragraph in the commit message. This seems to happen if the commit
196 # message includes '---' which would normally identify the start of
197 # comments. If the Change-Id is not in the last paragraph, uploading
198 # the patch is rejected by Gerrit. Force-move the Change-Id to the end
Guenter Roecka3182e22020-07-02 11:31:26 -0700199 # of the commit message to solve the problem. This conveniently also
200 # replaces the auto-generated Change-Id with the optional Change-Id
201 # passed as parameter.
Guenter Roeck9a5adc32020-07-24 08:13:36 -0700202 commit_message = get_chrome_commit_message('HEAD')
Guenter Roeckf82d0182020-06-19 07:50:01 -0700203 commit_message = re.sub(r'Change-Id:.*\n?', '', commit_message)
Guenter Roeck9a5adc32020-07-24 08:13:36 -0700204 commit_message = commit_message.rstrip()
205 commit_message += '\nChange-Id: %s' % fixer_changeid
Guenter Roeckf82d0182020-06-19 07:50:01 -0700206 subprocess.run(['git', 'commit', '--amend', '-m', commit_message], check=True)
Hirthanan Subenderanb1866552020-03-20 14:01:14 -0700207
Hirthanan Subenderanbbafac42020-04-10 16:18:16 -0700208 git_push_cmd = get_git_push_cmd(chromeos_branch, reviewers)
209 subprocess.run(git_push_cmd.split(' '), check=True)
Hirthanan Subenderanb1866552020-03-20 14:01:14 -0700210
211 return fixer_changeid
212 except subprocess.CalledProcessError as e:
213 raise ValueError('Failed to cherrypick and push upstream fix %s on branch %s'
214 % (fixer_upstream_sha, chromeos_branch)) from e
215 finally:
Hirthanan Subenderana90d8f82020-04-17 15:47:05 -0700216 checkout_and_clean(chrome_absolute_path, chromeos_branch)
Hirthanan Subenderanb1866552020-03-20 14:01:14 -0700217 os.chdir(cwd)
Guenter Roeck85c10bb2020-04-16 15:36:47 -0700218
219
220def search_subject_in_branch(merge_base, sha):
221 """Check if sha subject line is in the current branch.
222
223 Assumes function is run from correct directory/branch.
224 """
225
226 try:
227 # Retrieve subject line of provided SHA
228 cmd = ['git', 'log', '--pretty=format:%s', '-n', '1', sha]
229 subject = subprocess.check_output(cmd, encoding='utf-8', errors='ignore')
230 except subprocess.CalledProcessError:
231 logging.error('Error locating subject for sha %s', sha)
232 raise
233
234 try:
Guenter Roeck32b33aa2020-05-28 09:56:26 -0700235 cmd = ['git', 'log', '--no-merges', '-F', '--grep', subject,
Guenter Roeck85c10bb2020-04-16 15:36:47 -0700236 '%s..' % merge_base]
237 result = subprocess.check_output(cmd, stderr=subprocess.DEVNULL)
238 return bool(result)
239 except subprocess.CalledProcessError:
240 logging.error('Error while searching for subject "%s"', subject)
241 raise
242
243
244def get_cherrypick_status(repository, merge_base, branch, sha):
245 """cherry-pick provided sha into provided repository and branch.
246
247 Return Status Enum:
248 MERGED if the patch has already been applied,
249 OPEN if the patch is missing and applies cleanly,
250 CONFLICT if the patch is missing and fails to apply.
251 """
252 # Save current working directory
253 cwd = os.getcwd()
254
255 # Switch to repository directory to apply cherry-pick
256 absolute_path = common.get_kernel_absolute_path(repository)
257
258 os.chdir(absolute_path)
259 checkout_and_clean(absolute_path, branch)
260
261 ret = None
262 try:
263 applied = search_subject_in_branch(merge_base, sha)
264 if applied:
265 ret = common.Status.MERGED
266 raise ValueError
267
268 result = subprocess.call(['git', 'cherry-pick', '-n', sha],
269 stdout=subprocess.DEVNULL,
270 stderr=subprocess.DEVNULL)
271 if result:
272 ret = common.Status.CONFLICT
273 raise ValueError
274
275 diff = subprocess.check_output(['git', 'diff', 'HEAD'])
276 if diff:
277 ret = common.Status.OPEN
278 raise ValueError
279
280 ret = common.Status.MERGED
281
282 except ValueError:
283 pass
284
285 except subprocess.CalledProcessError:
286 ret = common.Status.CONFLICT
287
288 finally:
289 checkout_and_clean(absolute_path, branch)
290 os.chdir(cwd)
291
292 return ret
Guenter Roeckd9b6c6c2020-04-16 10:06:46 -0700293
294
295# match "vX.Y[.Z][.rcN]"
296version = re.compile(r'(v[0-9]+(?:\.[0-9]+)+(?:-rc[0-9]+)?)\s*')
297
298def get_integrated_tag(sha):
299 """For a given SHA, find the first tag that includes it."""
300
301 try:
302 path = common.get_kernel_absolute_path(common.UPSTREAM_PATH)
303 cmd = ['git', '-C', path, 'describe', '--match', 'v*',
304 '--contains', sha]
305 tag = subprocess.check_output(cmd, encoding='utf-8',
306 stderr=subprocess.DEVNULL)
307 return version.match(tag).group()
308 except AttributeError:
309 return None
310 except subprocess.CalledProcessError:
311 return None