Hirthanan Subenderan | b186655 | 2020-03-20 14:01:14 -0700 | [diff] [blame] | 1 | #!/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 | |
| 10 | i.e Parsing git logs for change-id, full commit sha's, etc. |
| 11 | """ |
| 12 | |
| 13 | from __future__ import print_function |
Guenter Roeck | 85c10bb | 2020-04-16 15:36:47 -0700 | [diff] [blame] | 14 | import logging |
Hirthanan Subenderan | b186655 | 2020-03-20 14:01:14 -0700 | [diff] [blame] | 15 | import re |
| 16 | import subprocess |
| 17 | import common |
| 18 | |
| 19 | |
Guenter Roeck | abea3d2 | 2021-01-21 07:14:46 -0800 | [diff] [blame] | 20 | def _git_check_output(path, command): |
| 21 | git_cmd = ['git', '-C', path] + command |
| 22 | return subprocess.check_output(git_cmd, encoding='utf-8', errors='ignore', |
| 23 | stderr=subprocess.DEVNULL) |
| 24 | |
| 25 | |
Hirthanan Subenderan | b186655 | 2020-03-20 14:01:14 -0700 | [diff] [blame] | 26 | def get_upstream_fullsha(abbrev_sha): |
| 27 | """Returns the full upstream sha for an abbreviated 12 digit sha using git cli""" |
| 28 | upstream_absolute_path = common.get_kernel_absolute_path(common.UPSTREAM_PATH) |
| 29 | try: |
Guenter Roeck | abea3d2 | 2021-01-21 07:14:46 -0800 | [diff] [blame] | 30 | cmd = ['rev-parse', abbrev_sha] |
| 31 | full_sha = _git_check_output(upstream_absolute_path, cmd) |
Hirthanan Subenderan | b186655 | 2020-03-20 14:01:14 -0700 | [diff] [blame] | 32 | return full_sha.rstrip() |
| 33 | except subprocess.CalledProcessError as e: |
| 34 | raise type(e)('Could not find full upstream sha for %s' % abbrev_sha, e.cmd) from e |
| 35 | |
| 36 | |
Guenter Roeck | 32b23dc | 2021-01-17 13:29:06 -0800 | [diff] [blame] | 37 | def _get_commit_message(kernel_path, sha): |
Hirthanan Subenderan | d6922c3 | 2020-03-23 14:17:40 -0700 | [diff] [blame] | 38 | """Returns the commit message for a sha in a given local path to kernel.""" |
Hirthanan Subenderan | b186655 | 2020-03-20 14:01:14 -0700 | [diff] [blame] | 39 | try: |
Guenter Roeck | abea3d2 | 2021-01-21 07:14:46 -0800 | [diff] [blame] | 40 | cmd = ['log', '--format=%B', '-n', '1', sha] |
| 41 | commit_message = _git_check_output(kernel_path, cmd) |
Hirthanan Subenderan | 3450f51 | 2020-04-09 22:36:50 -0700 | [diff] [blame] | 42 | |
| 43 | # Single newline following commit message |
| 44 | return commit_message.rstrip() + '\n' |
Hirthanan Subenderan | b186655 | 2020-03-20 14:01:14 -0700 | [diff] [blame] | 45 | except subprocess.CalledProcessError as e: |
Hirthanan Subenderan | d6922c3 | 2020-03-23 14:17:40 -0700 | [diff] [blame] | 46 | raise type(e)('Couldnt retrieve commit in kernel path %s for sha %s' |
| 47 | % (kernel_path, sha), e.cmd) from e |
| 48 | |
| 49 | |
| 50 | def get_upstream_commit_message(upstream_sha): |
| 51 | """Returns the commit message for a given upstream sha using git cli.""" |
| 52 | upstream_absolute_path = common.get_kernel_absolute_path(common.UPSTREAM_PATH) |
Guenter Roeck | 32b23dc | 2021-01-17 13:29:06 -0800 | [diff] [blame] | 53 | return _get_commit_message(upstream_absolute_path, upstream_sha) |
Hirthanan Subenderan | d6922c3 | 2020-03-23 14:17:40 -0700 | [diff] [blame] | 54 | |
| 55 | |
| 56 | def get_chrome_commit_message(chrome_sha): |
| 57 | """Returns the commit message for a given chrome sha using git cli.""" |
| 58 | chrome_absolute_path = common.get_kernel_absolute_path(common.CHROMEOS_PATH) |
Guenter Roeck | 32b23dc | 2021-01-17 13:29:06 -0800 | [diff] [blame] | 59 | return _get_commit_message(chrome_absolute_path, chrome_sha) |
Hirthanan Subenderan | b186655 | 2020-03-20 14:01:14 -0700 | [diff] [blame] | 60 | |
| 61 | |
Guenter Roeck | bfda124 | 2020-12-29 15:24:18 -0800 | [diff] [blame] | 62 | def get_merge_sha(branch, sha): |
| 63 | """Returns SHA of merge commit for provided SHA if available""" |
| 64 | |
| 65 | chrome_absolute_path = common.get_kernel_absolute_path(common.CHROMEOS_PATH) |
| 66 | |
| 67 | try: |
| 68 | # Get list of merges in <branch> since <sha> |
Guenter Roeck | abea3d2 | 2021-01-21 07:14:46 -0800 | [diff] [blame] | 69 | cmd = ['log', '--format=%h', '--abbrev=12', '--ancestry-path', '--merges', |
| 70 | '%s..%s' % (sha, branch)] |
| 71 | sha_list = _git_check_output(chrome_absolute_path, cmd) |
Guenter Roeck | bfda124 | 2020-12-29 15:24:18 -0800 | [diff] [blame] | 72 | if not sha_list: |
| 73 | logging.info('No merge commit for sha %s in branch %s', sha, branch) |
| 74 | return None |
| 75 | # merge_sha is our presumed merge commit |
| 76 | merge_sha = sha_list.splitlines()[-1] |
| 77 | # Verify if <sha> is indeed part of the merge |
Guenter Roeck | abea3d2 | 2021-01-21 07:14:46 -0800 | [diff] [blame] | 78 | cmd = ['log', '--format=%h', '--abbrev=12', '%s~1..%s' % (merge_sha, merge_sha)] |
| 79 | sha_list = _git_check_output(chrome_absolute_path, cmd) |
Guenter Roeck | bfda124 | 2020-12-29 15:24:18 -0800 | [diff] [blame] | 80 | if sha_list and sha in sha_list.splitlines(): |
| 81 | return merge_sha |
| 82 | logging.info('Merge commit for sha %s found as %s, but sha is missing in merge', |
| 83 | sha, merge_sha) |
| 84 | |
| 85 | except subprocess.CalledProcessError as e: |
| 86 | logging.info('Error "%s" while trying to find merge commit for sha %s in branch %s', |
| 87 | e, sha, branch) |
| 88 | |
| 89 | return None |
| 90 | |
| 91 | |
Hirthanan Subenderan | b186655 | 2020-03-20 14:01:14 -0700 | [diff] [blame] | 92 | def get_commit_changeid_linux_chrome(kernel_sha): |
Hirthanan Subenderan | d6922c3 | 2020-03-23 14:17:40 -0700 | [diff] [blame] | 93 | """Returns the changeid of the kernel_sha commit by parsing linux_chrome git log. |
| 94 | |
| 95 | kernel_sha will be one of linux_stable or linux_chrome commits. |
| 96 | """ |
Hirthanan Subenderan | b186655 | 2020-03-20 14:01:14 -0700 | [diff] [blame] | 97 | chrome_absolute_path = common.get_kernel_absolute_path(common.CHROMEOS_PATH) |
| 98 | try: |
Guenter Roeck | abea3d2 | 2021-01-21 07:14:46 -0800 | [diff] [blame] | 99 | cmd = ['log', '--format=%B', '-n', '1', kernel_sha] |
| 100 | commit_message = _git_check_output(chrome_absolute_path, cmd) |
Hirthanan Subenderan | b186655 | 2020-03-20 14:01:14 -0700 | [diff] [blame] | 101 | |
| 102 | m = re.findall('^Change-Id: (I[a-z0-9]{40})$', commit_message, re.M) |
| 103 | |
| 104 | # Get last change-id in case chrome sha cherry-picked/reverted into new commit |
| 105 | return m[-1] |
| 106 | except subprocess.CalledProcessError as e: |
Hirthanan Subenderan | d6922c3 | 2020-03-23 14:17:40 -0700 | [diff] [blame] | 107 | raise type(e)('Couldnt retrieve changeid for commit %s' % kernel_sha, e.cmd) from e |
Hirthanan Subenderan | b186655 | 2020-03-20 14:01:14 -0700 | [diff] [blame] | 108 | except IndexError as e: |
Hirthanan Subenderan | d6922c3 | 2020-03-23 14:17:40 -0700 | [diff] [blame] | 109 | # linux_stable kernel_sha's do not have an associated ChangeID |
| 110 | return None |
Hirthanan Subenderan | b186655 | 2020-03-20 14:01:14 -0700 | [diff] [blame] | 111 | |
| 112 | |
Guenter Roeck | 949d05b | 2020-05-12 12:35:36 -0700 | [diff] [blame] | 113 | def get_tag_emails_linux_chrome(sha): |
| 114 | """Returns unique list of chromium.org or google.com e-mails. |
| 115 | |
| 116 | The returned lust of e-mails is associated with tags found after |
| 117 | the last 'cherry picked from commit' message in the commit identified |
| 118 | by sha. Tags and e-mails are found by parsing the commit log. |
| 119 | |
| 120 | sha is expected to be be a commit in linux_stable or in linux_chrome. |
| 121 | """ |
| 122 | absolute_path = common.get_kernel_absolute_path(common.CHROMEOS_PATH) |
| 123 | try: |
Guenter Roeck | abea3d2 | 2021-01-21 07:14:46 -0800 | [diff] [blame] | 124 | cmd = ['log', '--format=%B', '-n', '1', sha] |
| 125 | commit_message = _git_check_output(absolute_path, cmd) |
Guenter Roeck | 949d05b | 2020-05-12 12:35:36 -0700 | [diff] [blame] | 126 | # If the commit has been cherry-picked, use subsequent tags to create |
| 127 | # list of reviewers. Otherwise, use all tags. Either case, only return |
| 128 | # e-mail addresses from Google domains. |
| 129 | s = commit_message.split('cherry picked from commit') |
| 130 | tags = 'Signed-off-by|Reviewed-by|Tested-by|Commit-Queue' |
| 131 | domains = 'chromium.org|google.com' |
| 132 | m = '^(?:%s): .* <(.*@(?:%s))>$' % (tags, domains) |
| 133 | emails = re.findall(m, s[-1], re.M) |
| 134 | if not emails: |
| 135 | # Final fallback: In some situations, "cherry picked from" |
| 136 | # is at the very end of the commit description, with no |
| 137 | # subsequent tags. If that happens, look for tags in the |
| 138 | # entire description. |
| 139 | emails = re.findall(m, commit_message, re.M) |
| 140 | return list(set(emails)) |
| 141 | except subprocess.CalledProcessError as e: |
| 142 | raise type(e)('Could not retrieve tag e-mails for commit %s' % sha, e.cmd) from e |
| 143 | except IndexError: |
| 144 | # sha does do not have a recognized tag |
| 145 | return None |
| 146 | |
| 147 | |
Guenter Roeck | d9b6c6c | 2020-04-16 10:06:46 -0700 | [diff] [blame] | 148 | # match "vX.Y[.Z][.rcN]" |
| 149 | version = re.compile(r'(v[0-9]+(?:\.[0-9]+)+(?:-rc[0-9]+)?)\s*') |
| 150 | |
| 151 | def get_integrated_tag(sha): |
Guenter Roeck | 32b23dc | 2021-01-17 13:29:06 -0800 | [diff] [blame] | 152 | """For a given SHA, find the first upstream tag that includes it.""" |
Guenter Roeck | d9b6c6c | 2020-04-16 10:06:46 -0700 | [diff] [blame] | 153 | |
| 154 | try: |
| 155 | path = common.get_kernel_absolute_path(common.UPSTREAM_PATH) |
Guenter Roeck | abea3d2 | 2021-01-21 07:14:46 -0800 | [diff] [blame] | 156 | cmd = ['describe', '--match', 'v*', '--contains', sha] |
| 157 | tag = _git_check_output(path, cmd) |
Guenter Roeck | d9b6c6c | 2020-04-16 10:06:46 -0700 | [diff] [blame] | 158 | return version.match(tag).group() |
| 159 | except AttributeError: |
| 160 | return None |
| 161 | except subprocess.CalledProcessError: |
| 162 | return None |
Guenter Roeck | 32b23dc | 2021-01-17 13:29:06 -0800 | [diff] [blame] | 163 | |
| 164 | |
| 165 | class commitHandler: |
| 166 | """Class to control active accesses on a git repository""" |
| 167 | |
Guenter Roeck | e3f32ae | 2021-01-26 19:03:45 -0800 | [diff] [blame] | 168 | commit_list = { } |
| 169 | |
Guenter Roeck | 20686c2 | 2021-01-25 09:39:10 -0800 | [diff] [blame] | 170 | def __init__(self, kernel, branch=None, full_reset=True): |
Guenter Roeck | e3f32ae | 2021-01-26 19:03:45 -0800 | [diff] [blame] | 171 | self.kernel = kernel |
Guenter Roeck | 32b23dc | 2021-01-17 13:29:06 -0800 | [diff] [blame] | 172 | self.metadata = common.get_kernel_metadata(kernel) |
| 173 | if not branch: |
| 174 | branch = self.metadata.branches[0] |
| 175 | self.branch = branch |
Guenter Roeck | 32b23dc | 2021-01-17 13:29:06 -0800 | [diff] [blame] | 176 | self.branchname = self.metadata.get_kernel_branch(branch) |
| 177 | self.path = common.get_kernel_absolute_path(self.metadata.path) |
| 178 | self.status = 'unknown' |
Guenter Roeck | 20686c2 | 2021-01-25 09:39:10 -0800 | [diff] [blame] | 179 | self.full_reset = full_reset |
Guenter Roeck | 32b23dc | 2021-01-17 13:29:06 -0800 | [diff] [blame] | 180 | |
Guenter Roeck | e3f32ae | 2021-01-26 19:03:45 -0800 | [diff] [blame] | 181 | if kernel not in self.commit_list: |
| 182 | self.commit_list[kernel] = { } |
| 183 | |
Guenter Roeck | 19a4be8 | 2021-01-20 14:01:53 -0800 | [diff] [blame] | 184 | current_branch_cmd = ['symbolic-ref', '-q', '--short', 'HEAD'] |
| 185 | self.current_branch = self.__git_check_output(current_branch_cmd).rstrip() |
| 186 | |
Guenter Roeck | 95e8893 | 2021-01-25 11:28:54 -0800 | [diff] [blame^] | 187 | def __base_tag(self): |
| 188 | """Return base tag for selected branch |
| 189 | |
| 190 | The base tag is derived from the tag template in metadata or, |
| 191 | if the tag template is empty, from the most recent tag in the |
| 192 | selected branch. |
| 193 | """ |
| 194 | if self.metadata.tag_template: |
| 195 | return self.metadata.tag_template % self.branch |
| 196 | # Pick base tag from most recent tag in branch (self.branchname) |
| 197 | cmd = ['describe', '--abbrev=0', self.branchname] |
| 198 | return self.__git_check_output(cmd).rstrip() |
| 199 | |
Guenter Roeck | 19a4be8 | 2021-01-20 14:01:53 -0800 | [diff] [blame] | 200 | def __git_command(self, command): |
| 201 | return ['git', '-C', self.path] + command |
| 202 | |
| 203 | def __git_check_output(self, command): |
| 204 | cmd = self.__git_command(command) |
| 205 | return subprocess.check_output(cmd, encoding='utf-8', errors='ignore') |
| 206 | |
| 207 | def __git_run(self, command): |
| 208 | cmd = self.__git_command(command) |
| 209 | subprocess.run(cmd, check=True) |
Guenter Roeck | 32b23dc | 2021-01-17 13:29:06 -0800 | [diff] [blame] | 210 | |
Guenter Roeck | 4a39042 | 2021-01-20 12:33:04 -0800 | [diff] [blame] | 211 | def __reset_hard_ref(self, reference): |
| 212 | """Force reset to provided reference""" |
Guenter Roeck | 19a4be8 | 2021-01-20 14:01:53 -0800 | [diff] [blame] | 213 | reset_cmd = ['reset', '-q', '--hard', reference] |
| 214 | self.__git_run(reset_cmd) |
Guenter Roeck | 4a39042 | 2021-01-20 12:33:04 -0800 | [diff] [blame] | 215 | |
| 216 | def __reset_hard_head(self): |
| 217 | """Force hard reset to git head in checked out branch""" |
| 218 | self.__reset_hard_ref('HEAD') |
| 219 | |
| 220 | def __reset_hard_origin(self): |
| 221 | """Force hard reset to head of remote branch""" |
| 222 | self.__reset_hard_ref('origin/%s' % self.branchname) |
| 223 | |
Guenter Roeck | 32b23dc | 2021-01-17 13:29:06 -0800 | [diff] [blame] | 224 | def __checkout_and_clean(self): |
| 225 | """Clean up uncommitted files in branch and checkout to be up to date with origin.""" |
Guenter Roeck | 19a4be8 | 2021-01-20 14:01:53 -0800 | [diff] [blame] | 226 | clean_untracked = ['clean', '-d', '-x', '-f', '-q'] |
| 227 | checkout = ['checkout', '-q', self.branchname] |
Guenter Roeck | 32b23dc | 2021-01-17 13:29:06 -0800 | [diff] [blame] | 228 | |
Guenter Roeck | 4a39042 | 2021-01-20 12:33:04 -0800 | [diff] [blame] | 229 | self.__reset_hard_head() |
Guenter Roeck | 19a4be8 | 2021-01-20 14:01:53 -0800 | [diff] [blame] | 230 | self.__git_run(clean_untracked) |
Guenter Roeck | 32b23dc | 2021-01-17 13:29:06 -0800 | [diff] [blame] | 231 | |
| 232 | if self.current_branch != self.branchname: |
Guenter Roeck | 19a4be8 | 2021-01-20 14:01:53 -0800 | [diff] [blame] | 233 | self.__git_run(checkout) |
Guenter Roeck | 32b23dc | 2021-01-17 13:29:06 -0800 | [diff] [blame] | 234 | self.current_branch = self.branchname |
| 235 | |
Guenter Roeck | 20686c2 | 2021-01-25 09:39:10 -0800 | [diff] [blame] | 236 | if self.full_reset: |
| 237 | self.__reset_hard_origin() |
Guenter Roeck | 32b23dc | 2021-01-17 13:29:06 -0800 | [diff] [blame] | 238 | |
Guenter Roeck | 012a69f | 2021-01-22 10:25:30 -0800 | [diff] [blame] | 239 | def __setup(self, branch=None): |
| 240 | """Local setup function, to be called for each access. |
| 241 | |
| 242 | Also sets the active branch if provided. |
| 243 | """ |
| 244 | if branch and branch != self.branch: |
| 245 | self.branch = branch |
Guenter Roeck | 012a69f | 2021-01-22 10:25:30 -0800 | [diff] [blame] | 246 | self.branchname = self.metadata.get_kernel_branch(branch) |
| 247 | self.status = 'unknown' |
| 248 | |
Guenter Roeck | 32b23dc | 2021-01-17 13:29:06 -0800 | [diff] [blame] | 249 | if self.status == 'unknown': |
| 250 | self.__checkout_and_clean() |
| 251 | elif self.status == 'changed': |
Guenter Roeck | 4a39042 | 2021-01-20 12:33:04 -0800 | [diff] [blame] | 252 | self.__reset_hard_origin() |
Guenter Roeck | 32b23dc | 2021-01-17 13:29:06 -0800 | [diff] [blame] | 253 | |
| 254 | self.status = 'clean' |
| 255 | |
| 256 | def __search_subject(self, sha): |
| 257 | """Check if subject associated with 'sha' exists in the current branch""" |
| 258 | |
| 259 | try: |
| 260 | # Retrieve subject line of provided SHA |
Guenter Roeck | 19a4be8 | 2021-01-20 14:01:53 -0800 | [diff] [blame] | 261 | cmd = ['log', '--pretty=format:%s', '-n', '1', sha] |
| 262 | subject = self.__git_check_output(cmd) |
Guenter Roeck | 32b23dc | 2021-01-17 13:29:06 -0800 | [diff] [blame] | 263 | except subprocess.CalledProcessError: |
| 264 | logging.error('Failed to get subject for sha %s', sha) |
| 265 | return False |
| 266 | |
Guenter Roeck | e3f32ae | 2021-01-26 19:03:45 -0800 | [diff] [blame] | 267 | commit_list = self.commit_list[self.kernel] |
| 268 | if self.branch not in commit_list: |
Guenter Roeck | 19a4be8 | 2021-01-20 14:01:53 -0800 | [diff] [blame] | 269 | cmd = ['log', '--no-merges', '--format=%s', |
Guenter Roeck | 95e8893 | 2021-01-25 11:28:54 -0800 | [diff] [blame^] | 270 | '%s..%s' % (self.__base_tag(), self.branchname)] |
Guenter Roeck | 19a4be8 | 2021-01-20 14:01:53 -0800 | [diff] [blame] | 271 | subjects = self.__git_check_output(cmd) |
Guenter Roeck | e3f32ae | 2021-01-26 19:03:45 -0800 | [diff] [blame] | 272 | commit_list[self.branch] = subjects.splitlines() |
Guenter Roeck | 32b23dc | 2021-01-17 13:29:06 -0800 | [diff] [blame] | 273 | |
| 274 | # The following is a raw search which will match, for example, a revert of a commit. |
| 275 | # A better method to check if commits have been applied would be desirable. |
Guenter Roeck | e3f32ae | 2021-01-26 19:03:45 -0800 | [diff] [blame] | 276 | subjects = commit_list[self.branch] |
Guenter Roeck | 32b23dc | 2021-01-17 13:29:06 -0800 | [diff] [blame] | 277 | return any(subject in s for s in subjects) |
| 278 | |
| 279 | def __get_git_push_cmd(self, reviewers): |
| 280 | """Generates git push command with added reviewers and autogenerated tag. |
| 281 | |
| 282 | Read more about gerrit tags here: |
| 283 | https://gerrit-review.googlesource.com/Documentation/cmd-receive-pack.html |
| 284 | """ |
Guenter Roeck | 32b23dc | 2021-01-17 13:29:06 -0800 | [diff] [blame] | 285 | reviewers_tag = ['r=%s'% r for r in reviewers] |
| 286 | autogenerated_tag = ['t=autogenerated'] |
| 287 | tags = ','.join(reviewers_tag + autogenerated_tag) |
Guenter Roeck | 61e294f | 2021-01-20 14:44:46 -0800 | [diff] [blame] | 288 | head = 'HEAD:refs/for/%s%%%s' % (self.branchname, tags) |
| 289 | return ['push', 'origin', head] |
Guenter Roeck | 32b23dc | 2021-01-17 13:29:06 -0800 | [diff] [blame] | 290 | |
Guenter Roeck | 76b864c | 2021-01-20 15:24:16 -0800 | [diff] [blame] | 291 | def fetch(self, remote=None): |
| 292 | """Fetch changes from provided remote or from origin""" |
| 293 | if not remote: |
| 294 | remote = 'origin' |
| 295 | self.__setup() |
| 296 | fetch_cmd = ['fetch', '-q', remote] |
| 297 | self.__git_run(fetch_cmd) |
| 298 | self.status = 'changed' |
| 299 | |
Guenter Roeck | 32b23dc | 2021-01-17 13:29:06 -0800 | [diff] [blame] | 300 | def pull(self, branch=None): |
| 301 | """Pull changes from remote repository into provided or default branch""" |
Guenter Roeck | 012a69f | 2021-01-22 10:25:30 -0800 | [diff] [blame] | 302 | self.__setup(branch) |
Guenter Roeck | 19a4be8 | 2021-01-20 14:01:53 -0800 | [diff] [blame] | 303 | pull_cmd = ['pull', '-q'] |
| 304 | self.__git_run(pull_cmd) |
Guenter Roeck | 32b23dc | 2021-01-17 13:29:06 -0800 | [diff] [blame] | 305 | |
| 306 | def cherry_pick_and_push(self, fixer_upstream_sha, fixer_changeid, fix_commit_message, |
| 307 | reviewers): |
| 308 | """Cherry picks upstream commit into chrome repo. |
| 309 | |
| 310 | Adds reviewers and autogenerated tag with the pushed commit. |
| 311 | """ |
| 312 | |
| 313 | self.__setup() |
| 314 | try: |
| 315 | self.status = 'changed' |
Guenter Roeck | 19a4be8 | 2021-01-20 14:01:53 -0800 | [diff] [blame] | 316 | self.__git_run(['cherry-pick', '-n', fixer_upstream_sha]) |
| 317 | self.__git_run(['commit', '-s', '-m', fix_commit_message]) |
Guenter Roeck | 32b23dc | 2021-01-17 13:29:06 -0800 | [diff] [blame] | 318 | |
| 319 | # commit has been cherry-picked and committed locally, precommit hook |
| 320 | # in git repository adds changeid to the commit message. Pick it unless |
| 321 | # we already have one passed as parameter. |
| 322 | if not fixer_changeid: |
| 323 | fixer_changeid = get_commit_changeid_linux_chrome('HEAD') |
| 324 | |
| 325 | # Sometimes the commit hook doesn't attach the Change-Id to the last |
| 326 | # paragraph in the commit message. This seems to happen if the commit |
| 327 | # message includes '---' which would normally identify the start of |
| 328 | # comments. If the Change-Id is not in the last paragraph, uploading |
| 329 | # the patch is rejected by Gerrit. Force-move the Change-Id to the end |
| 330 | # of the commit message to solve the problem. This conveniently also |
| 331 | # replaces the auto-generated Change-Id with the optional Change-Id |
| 332 | # passed as parameter. |
| 333 | commit_message = get_chrome_commit_message('HEAD') |
| 334 | commit_message = re.sub(r'Change-Id:.*\n?', '', commit_message) |
| 335 | commit_message = commit_message.rstrip() |
| 336 | commit_message += '\nChange-Id: %s' % fixer_changeid |
Guenter Roeck | 19a4be8 | 2021-01-20 14:01:53 -0800 | [diff] [blame] | 337 | self.__git_run(['commit', '--amend', '-m', commit_message]) |
Guenter Roeck | 32b23dc | 2021-01-17 13:29:06 -0800 | [diff] [blame] | 338 | |
| 339 | git_push_cmd = self.__get_git_push_cmd(reviewers) |
Guenter Roeck | 61e294f | 2021-01-20 14:44:46 -0800 | [diff] [blame] | 340 | self.__git_run(git_push_cmd) |
Guenter Roeck | 32b23dc | 2021-01-17 13:29:06 -0800 | [diff] [blame] | 341 | |
| 342 | return fixer_changeid |
| 343 | except subprocess.CalledProcessError as e: |
| 344 | raise ValueError('Failed to cherrypick and push upstream fix %s on branch %s' |
| 345 | % (fixer_upstream_sha, self.branchname)) from e |
| 346 | finally: |
Guenter Roeck | 4a39042 | 2021-01-20 12:33:04 -0800 | [diff] [blame] | 347 | self.__reset_hard_head() |
Guenter Roeck | 32b23dc | 2021-01-17 13:29:06 -0800 | [diff] [blame] | 348 | self.status = 'changed' |
| 349 | |
| 350 | def cherrypick_status(self, sha, branch=None, apply=True): |
| 351 | """cherry-pick provided sha into repository and branch identified by this class instance |
| 352 | |
| 353 | Return Status Enum: |
| 354 | MERGED if the patch has already been applied, |
| 355 | OPEN if the patch is missing and applies cleanly, |
| 356 | CONFLICT if the patch is missing and fails to apply. |
| 357 | """ |
| 358 | |
Guenter Roeck | 012a69f | 2021-01-22 10:25:30 -0800 | [diff] [blame] | 359 | self.__setup(branch) |
Guenter Roeck | 32b23dc | 2021-01-17 13:29:06 -0800 | [diff] [blame] | 360 | |
| 361 | ret = None |
| 362 | try: |
| 363 | applied = self.__search_subject(sha) |
| 364 | if applied: |
| 365 | ret = common.Status.MERGED |
| 366 | raise ValueError |
| 367 | |
| 368 | if not apply: |
| 369 | raise ValueError |
| 370 | |
Guenter Roeck | 19a4be8 | 2021-01-20 14:01:53 -0800 | [diff] [blame] | 371 | result = subprocess.call(self.__git_command(['cherry-pick', '-n', sha]), |
Guenter Roeck | 32b23dc | 2021-01-17 13:29:06 -0800 | [diff] [blame] | 372 | stdout=subprocess.DEVNULL, |
| 373 | stderr=subprocess.DEVNULL) |
| 374 | if result: |
| 375 | ret = common.Status.CONFLICT |
| 376 | raise ValueError |
| 377 | |
Guenter Roeck | 19a4be8 | 2021-01-20 14:01:53 -0800 | [diff] [blame] | 378 | diff = self.__git_check_output(['diff', 'HEAD']) |
Guenter Roeck | 32b23dc | 2021-01-17 13:29:06 -0800 | [diff] [blame] | 379 | if diff: |
| 380 | ret = common.Status.OPEN |
| 381 | raise ValueError |
| 382 | |
| 383 | ret = common.Status.MERGED |
| 384 | |
| 385 | except ValueError: |
| 386 | pass |
| 387 | |
| 388 | except subprocess.CalledProcessError: |
| 389 | ret = common.Status.CONFLICT |
| 390 | |
| 391 | finally: |
Guenter Roeck | 4a39042 | 2021-01-20 12:33:04 -0800 | [diff] [blame] | 392 | self.__reset_hard_head() |
Guenter Roeck | 32b23dc | 2021-01-17 13:29:06 -0800 | [diff] [blame] | 393 | self.status = 'clean' |
| 394 | |
| 395 | return ret |