blob: 76d5f105c8c1eacea1bb297599a3b22fe778056e [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
70def get_commit_changeid_linux_chrome(kernel_sha):
Hirthanan Subenderand6922c32020-03-23 14:17:40 -070071 """Returns the changeid of the kernel_sha commit by parsing linux_chrome git log.
72
73 kernel_sha will be one of linux_stable or linux_chrome commits.
74 """
Hirthanan Subenderanb1866552020-03-20 14:01:14 -070075 chrome_absolute_path = common.get_kernel_absolute_path(common.CHROMEOS_PATH)
76 try:
77 cmd = ['git', '-C', chrome_absolute_path, 'log', '--format=%B', '-n', '1', kernel_sha]
Hirthanan Subenderanaffcc4a2020-03-25 14:42:33 -070078 commit_message = subprocess.check_output(cmd, encoding='utf-8', errors='ignore')
Hirthanan Subenderanb1866552020-03-20 14:01:14 -070079
80 m = re.findall('^Change-Id: (I[a-z0-9]{40})$', commit_message, re.M)
81
82 # Get last change-id in case chrome sha cherry-picked/reverted into new commit
83 return m[-1]
84 except subprocess.CalledProcessError as e:
Hirthanan Subenderand6922c32020-03-23 14:17:40 -070085 raise type(e)('Couldnt retrieve changeid for commit %s' % kernel_sha, e.cmd) from e
Hirthanan Subenderanb1866552020-03-20 14:01:14 -070086 except IndexError as e:
Hirthanan Subenderand6922c32020-03-23 14:17:40 -070087 # linux_stable kernel_sha's do not have an associated ChangeID
88 return None
Hirthanan Subenderanb1866552020-03-20 14:01:14 -070089
90
Guenter Roeck949d05b2020-05-12 12:35:36 -070091def get_tag_emails_linux_chrome(sha):
92 """Returns unique list of chromium.org or google.com e-mails.
93
94 The returned lust of e-mails is associated with tags found after
95 the last 'cherry picked from commit' message in the commit identified
96 by sha. Tags and e-mails are found by parsing the commit log.
97
98 sha is expected to be be a commit in linux_stable or in linux_chrome.
99 """
100 absolute_path = common.get_kernel_absolute_path(common.CHROMEOS_PATH)
101 try:
102 cmd = ['git', '-C', absolute_path, 'log', '--format=%B', '-n', '1', sha]
103 commit_message = subprocess.check_output(cmd, encoding='utf-8', errors='ignore')
104 # If the commit has been cherry-picked, use subsequent tags to create
105 # list of reviewers. Otherwise, use all tags. Either case, only return
106 # e-mail addresses from Google domains.
107 s = commit_message.split('cherry picked from commit')
108 tags = 'Signed-off-by|Reviewed-by|Tested-by|Commit-Queue'
109 domains = 'chromium.org|google.com'
110 m = '^(?:%s): .* <(.*@(?:%s))>$' % (tags, domains)
111 emails = re.findall(m, s[-1], re.M)
112 if not emails:
113 # Final fallback: In some situations, "cherry picked from"
114 # is at the very end of the commit description, with no
115 # subsequent tags. If that happens, look for tags in the
116 # entire description.
117 emails = re.findall(m, commit_message, re.M)
118 return list(set(emails))
119 except subprocess.CalledProcessError as e:
120 raise type(e)('Could not retrieve tag e-mails for commit %s' % sha, e.cmd) from e
121 except IndexError:
122 # sha does do not have a recognized tag
123 return None
124
125
Hirthanan Subenderanb1866552020-03-20 14:01:14 -0700126def get_last_commit_sha_linux_chrome():
127 """Retrieves the last SHA in linux_chrome repository."""
128 chrome_absolute_path = common.get_kernel_absolute_path(common.CHROMEOS_PATH)
129 try:
130 cmd = ['git', '-C', chrome_absolute_path, 'rev-parse', 'HEAD']
Hirthanan Subenderanaffcc4a2020-03-25 14:42:33 -0700131 last_commit = subprocess.check_output(cmd, encoding='utf-8')
Hirthanan Subenderanb1866552020-03-20 14:01:14 -0700132 return last_commit.rstrip()
133 except subprocess.CalledProcessError as e:
134 raise type(e)('Couldnt retrieve most recent commit in linux_chrome', e.cmd) from e
135
136
Hirthanan Subenderanbbafac42020-04-10 16:18:16 -0700137def get_git_push_cmd(chromeos_branch, reviewers):
138 """Generates git push command with added reviewers and autogenerated tag.
139
140 Read more about gerrit tags here:
141 https://gerrit-review.googlesource.com/Documentation/cmd-receive-pack.html
142 """
143 git_push_head = 'git push origin HEAD:refs/for/%s' % chromeos_branch
144 reviewers_tag = ['r=%s'% r for r in reviewers]
145 autogenerated_tag = ['t=autogenerated']
146 tags = ','.join(reviewers_tag + autogenerated_tag)
147 return git_push_head + '%' + tags
148
149
150def cherry_pick_and_push_fix(fixer_upstream_sha, chromeos_branch,
151 fix_commit_message, reviewers):
152 """Cherry picks upstream commit into chrome repo.
153
154 Adds reviewers and autogenerated tag with the pushed commit.
155 """
Hirthanan Subenderanb1866552020-03-20 14:01:14 -0700156 cwd = os.getcwd()
157 chrome_absolute_path = common.get_kernel_absolute_path(common.CHROMEOS_PATH)
158
159 # reset linux_chrome repo to remove local changes
160 try:
161 os.chdir(chrome_absolute_path)
Hirthanan Subenderana90d8f82020-04-17 15:47:05 -0700162 checkout_and_clean(chrome_absolute_path, chromeos_branch)
Hirthanan Subenderanbbafac42020-04-10 16:18:16 -0700163 subprocess.run(['git', 'cherry-pick', '-n', fixer_upstream_sha], check=True)
164 subprocess.run(['git', 'commit', '-s', '-m', fix_commit_message], check=True)
Hirthanan Subenderanb1866552020-03-20 14:01:14 -0700165
166 # commit has been cherry-picked and committed locally, precommit hook
167 # in git repository adds changeid to the commit message
168 last_commit = get_last_commit_sha_linux_chrome()
169 fixer_changeid = get_commit_changeid_linux_chrome(last_commit)
170
Hirthanan Subenderanbbafac42020-04-10 16:18:16 -0700171 git_push_cmd = get_git_push_cmd(chromeos_branch, reviewers)
172 subprocess.run(git_push_cmd.split(' '), check=True)
Hirthanan Subenderanb1866552020-03-20 14:01:14 -0700173
174 return fixer_changeid
175 except subprocess.CalledProcessError as e:
176 raise ValueError('Failed to cherrypick and push upstream fix %s on branch %s'
177 % (fixer_upstream_sha, chromeos_branch)) from e
178 finally:
Hirthanan Subenderana90d8f82020-04-17 15:47:05 -0700179 checkout_and_clean(chrome_absolute_path, chromeos_branch)
Hirthanan Subenderanb1866552020-03-20 14:01:14 -0700180 os.chdir(cwd)
Guenter Roeck85c10bb2020-04-16 15:36:47 -0700181
182
183def search_subject_in_branch(merge_base, sha):
184 """Check if sha subject line is in the current branch.
185
186 Assumes function is run from correct directory/branch.
187 """
188
189 try:
190 # Retrieve subject line of provided SHA
191 cmd = ['git', 'log', '--pretty=format:%s', '-n', '1', sha]
192 subject = subprocess.check_output(cmd, encoding='utf-8', errors='ignore')
193 except subprocess.CalledProcessError:
194 logging.error('Error locating subject for sha %s', sha)
195 raise
196
197 try:
198 cmd = ['git', 'log', '--no-merges', '--grep', subject,
199 '%s..' % merge_base]
200 result = subprocess.check_output(cmd, stderr=subprocess.DEVNULL)
201 return bool(result)
202 except subprocess.CalledProcessError:
203 logging.error('Error while searching for subject "%s"', subject)
204 raise
205
206
207def get_cherrypick_status(repository, merge_base, branch, sha):
208 """cherry-pick provided sha into provided repository and branch.
209
210 Return Status Enum:
211 MERGED if the patch has already been applied,
212 OPEN if the patch is missing and applies cleanly,
213 CONFLICT if the patch is missing and fails to apply.
214 """
215 # Save current working directory
216 cwd = os.getcwd()
217
218 # Switch to repository directory to apply cherry-pick
219 absolute_path = common.get_kernel_absolute_path(repository)
220
221 os.chdir(absolute_path)
222 checkout_and_clean(absolute_path, branch)
223
224 ret = None
225 try:
226 applied = search_subject_in_branch(merge_base, sha)
227 if applied:
228 ret = common.Status.MERGED
229 raise ValueError
230
231 result = subprocess.call(['git', 'cherry-pick', '-n', sha],
232 stdout=subprocess.DEVNULL,
233 stderr=subprocess.DEVNULL)
234 if result:
235 ret = common.Status.CONFLICT
236 raise ValueError
237
238 diff = subprocess.check_output(['git', 'diff', 'HEAD'])
239 if diff:
240 ret = common.Status.OPEN
241 raise ValueError
242
243 ret = common.Status.MERGED
244
245 except ValueError:
246 pass
247
248 except subprocess.CalledProcessError:
249 ret = common.Status.CONFLICT
250
251 finally:
252 checkout_and_clean(absolute_path, branch)
253 os.chdir(cwd)
254
255 return ret