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