1bf215546Sopenharmony_ci# Copyright © 2019-2020 Intel Corporation 2bf215546Sopenharmony_ci 3bf215546Sopenharmony_ci# Permission is hereby granted, free of charge, to any person obtaining a copy 4bf215546Sopenharmony_ci# of this software and associated documentation files (the "Software"), to deal 5bf215546Sopenharmony_ci# in the Software without restriction, including without limitation the rights 6bf215546Sopenharmony_ci# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7bf215546Sopenharmony_ci# copies of the Software, and to permit persons to whom the Software is 8bf215546Sopenharmony_ci# furnished to do so, subject to the following conditions: 9bf215546Sopenharmony_ci 10bf215546Sopenharmony_ci# The above copyright notice and this permission notice shall be included in 11bf215546Sopenharmony_ci# all copies or substantial portions of the Software. 12bf215546Sopenharmony_ci 13bf215546Sopenharmony_ci# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14bf215546Sopenharmony_ci# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15bf215546Sopenharmony_ci# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16bf215546Sopenharmony_ci# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17bf215546Sopenharmony_ci# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18bf215546Sopenharmony_ci# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19bf215546Sopenharmony_ci# SOFTWARE. 20bf215546Sopenharmony_ci 21bf215546Sopenharmony_ci"""Core data structures and routines for pick.""" 22bf215546Sopenharmony_ci 23bf215546Sopenharmony_ciimport asyncio 24bf215546Sopenharmony_ciimport enum 25bf215546Sopenharmony_ciimport json 26bf215546Sopenharmony_ciimport pathlib 27bf215546Sopenharmony_ciimport re 28bf215546Sopenharmony_ciimport subprocess 29bf215546Sopenharmony_ciimport typing 30bf215546Sopenharmony_ci 31bf215546Sopenharmony_ciimport attr 32bf215546Sopenharmony_ci 33bf215546Sopenharmony_ciif typing.TYPE_CHECKING: 34bf215546Sopenharmony_ci from .ui import UI 35bf215546Sopenharmony_ci 36bf215546Sopenharmony_ci import typing_extensions 37bf215546Sopenharmony_ci 38bf215546Sopenharmony_ci class CommitDict(typing_extensions.TypedDict): 39bf215546Sopenharmony_ci 40bf215546Sopenharmony_ci sha: str 41bf215546Sopenharmony_ci description: str 42bf215546Sopenharmony_ci nominated: bool 43bf215546Sopenharmony_ci nomination_type: typing.Optional[int] 44bf215546Sopenharmony_ci resolution: typing.Optional[int] 45bf215546Sopenharmony_ci main_sha: typing.Optional[str] 46bf215546Sopenharmony_ci because_sha: typing.Optional[str] 47bf215546Sopenharmony_ci 48bf215546Sopenharmony_ciIS_FIX = re.compile(r'^\s*fixes:\s*([a-f0-9]{6,40})', flags=re.MULTILINE | re.IGNORECASE) 49bf215546Sopenharmony_ci# FIXME: I dislike the duplication in this regex, but I couldn't get it to work otherwise 50bf215546Sopenharmony_ciIS_CC = re.compile(r'^\s*cc:\s*["\']?([0-9]{2}\.[0-9])?["\']?\s*["\']?([0-9]{2}\.[0-9])?["\']?\s*\<?mesa-stable', 51bf215546Sopenharmony_ci flags=re.MULTILINE | re.IGNORECASE) 52bf215546Sopenharmony_ciIS_REVERT = re.compile(r'This reverts commit ([0-9a-f]{40})') 53bf215546Sopenharmony_ci 54bf215546Sopenharmony_ci# XXX: hack 55bf215546Sopenharmony_ciSEM = asyncio.Semaphore(50) 56bf215546Sopenharmony_ci 57bf215546Sopenharmony_ciCOMMIT_LOCK = asyncio.Lock() 58bf215546Sopenharmony_ci 59bf215546Sopenharmony_cigit_toplevel = subprocess.check_output(['git', 'rev-parse', '--show-toplevel'], 60bf215546Sopenharmony_ci stderr=subprocess.DEVNULL).decode("ascii").strip() 61bf215546Sopenharmony_cipick_status_json = pathlib.Path(git_toplevel) / '.pick_status.json' 62bf215546Sopenharmony_ci 63bf215546Sopenharmony_ci 64bf215546Sopenharmony_ciclass PickUIException(Exception): 65bf215546Sopenharmony_ci pass 66bf215546Sopenharmony_ci 67bf215546Sopenharmony_ci 68bf215546Sopenharmony_ci@enum.unique 69bf215546Sopenharmony_ciclass NominationType(enum.Enum): 70bf215546Sopenharmony_ci 71bf215546Sopenharmony_ci CC = 0 72bf215546Sopenharmony_ci FIXES = 1 73bf215546Sopenharmony_ci REVERT = 2 74bf215546Sopenharmony_ci 75bf215546Sopenharmony_ci 76bf215546Sopenharmony_ci@enum.unique 77bf215546Sopenharmony_ciclass Resolution(enum.Enum): 78bf215546Sopenharmony_ci 79bf215546Sopenharmony_ci UNRESOLVED = 0 80bf215546Sopenharmony_ci MERGED = 1 81bf215546Sopenharmony_ci DENOMINATED = 2 82bf215546Sopenharmony_ci BACKPORTED = 3 83bf215546Sopenharmony_ci NOTNEEDED = 4 84bf215546Sopenharmony_ci 85bf215546Sopenharmony_ci 86bf215546Sopenharmony_ciasync def commit_state(*, amend: bool = False, message: str = 'Update') -> bool: 87bf215546Sopenharmony_ci """Commit the .pick_status.json file.""" 88bf215546Sopenharmony_ci async with COMMIT_LOCK: 89bf215546Sopenharmony_ci p = await asyncio.create_subprocess_exec( 90bf215546Sopenharmony_ci 'git', 'add', pick_status_json.as_posix(), 91bf215546Sopenharmony_ci stdout=asyncio.subprocess.DEVNULL, 92bf215546Sopenharmony_ci stderr=asyncio.subprocess.DEVNULL, 93bf215546Sopenharmony_ci ) 94bf215546Sopenharmony_ci v = await p.wait() 95bf215546Sopenharmony_ci if v != 0: 96bf215546Sopenharmony_ci return False 97bf215546Sopenharmony_ci 98bf215546Sopenharmony_ci if amend: 99bf215546Sopenharmony_ci cmd = ['--amend', '--no-edit'] 100bf215546Sopenharmony_ci else: 101bf215546Sopenharmony_ci cmd = ['--message', f'.pick_status.json: {message}'] 102bf215546Sopenharmony_ci p = await asyncio.create_subprocess_exec( 103bf215546Sopenharmony_ci 'git', 'commit', *cmd, 104bf215546Sopenharmony_ci stdout=asyncio.subprocess.DEVNULL, 105bf215546Sopenharmony_ci stderr=asyncio.subprocess.DEVNULL, 106bf215546Sopenharmony_ci ) 107bf215546Sopenharmony_ci v = await p.wait() 108bf215546Sopenharmony_ci if v != 0: 109bf215546Sopenharmony_ci return False 110bf215546Sopenharmony_ci return True 111bf215546Sopenharmony_ci 112bf215546Sopenharmony_ci 113bf215546Sopenharmony_ci@attr.s(slots=True) 114bf215546Sopenharmony_ciclass Commit: 115bf215546Sopenharmony_ci 116bf215546Sopenharmony_ci sha: str = attr.ib() 117bf215546Sopenharmony_ci description: str = attr.ib() 118bf215546Sopenharmony_ci nominated: bool = attr.ib(False) 119bf215546Sopenharmony_ci nomination_type: typing.Optional[NominationType] = attr.ib(None) 120bf215546Sopenharmony_ci resolution: Resolution = attr.ib(Resolution.UNRESOLVED) 121bf215546Sopenharmony_ci main_sha: typing.Optional[str] = attr.ib(None) 122bf215546Sopenharmony_ci because_sha: typing.Optional[str] = attr.ib(None) 123bf215546Sopenharmony_ci 124bf215546Sopenharmony_ci def to_json(self) -> 'CommitDict': 125bf215546Sopenharmony_ci d: typing.Dict[str, typing.Any] = attr.asdict(self) 126bf215546Sopenharmony_ci if self.nomination_type is not None: 127bf215546Sopenharmony_ci d['nomination_type'] = self.nomination_type.value 128bf215546Sopenharmony_ci if self.resolution is not None: 129bf215546Sopenharmony_ci d['resolution'] = self.resolution.value 130bf215546Sopenharmony_ci return typing.cast('CommitDict', d) 131bf215546Sopenharmony_ci 132bf215546Sopenharmony_ci @classmethod 133bf215546Sopenharmony_ci def from_json(cls, data: 'CommitDict') -> 'Commit': 134bf215546Sopenharmony_ci c = cls(data['sha'], data['description'], data['nominated'], main_sha=data['main_sha'], because_sha=data['because_sha']) 135bf215546Sopenharmony_ci if data['nomination_type'] is not None: 136bf215546Sopenharmony_ci c.nomination_type = NominationType(data['nomination_type']) 137bf215546Sopenharmony_ci if data['resolution'] is not None: 138bf215546Sopenharmony_ci c.resolution = Resolution(data['resolution']) 139bf215546Sopenharmony_ci return c 140bf215546Sopenharmony_ci 141bf215546Sopenharmony_ci def date(self) -> str: 142bf215546Sopenharmony_ci # Show commit date, ie. when the commit actually landed 143bf215546Sopenharmony_ci # (as opposed to when it was first written) 144bf215546Sopenharmony_ci return subprocess.check_output( 145bf215546Sopenharmony_ci ['git', 'show', '--no-patch', '--format=%cs', self.sha], 146bf215546Sopenharmony_ci stderr=subprocess.DEVNULL 147bf215546Sopenharmony_ci ).decode("ascii").strip() 148bf215546Sopenharmony_ci 149bf215546Sopenharmony_ci async def apply(self, ui: 'UI') -> typing.Tuple[bool, str]: 150bf215546Sopenharmony_ci # FIXME: This isn't really enough if we fail to cherry-pick because the 151bf215546Sopenharmony_ci # git tree will still be dirty 152bf215546Sopenharmony_ci async with COMMIT_LOCK: 153bf215546Sopenharmony_ci p = await asyncio.create_subprocess_exec( 154bf215546Sopenharmony_ci 'git', 'cherry-pick', '-x', self.sha, 155bf215546Sopenharmony_ci stdout=asyncio.subprocess.DEVNULL, 156bf215546Sopenharmony_ci stderr=asyncio.subprocess.PIPE, 157bf215546Sopenharmony_ci ) 158bf215546Sopenharmony_ci _, err = await p.communicate() 159bf215546Sopenharmony_ci 160bf215546Sopenharmony_ci if p.returncode != 0: 161bf215546Sopenharmony_ci return (False, err.decode()) 162bf215546Sopenharmony_ci 163bf215546Sopenharmony_ci self.resolution = Resolution.MERGED 164bf215546Sopenharmony_ci await ui.feedback(f'{self.sha} ({self.description}) applied successfully') 165bf215546Sopenharmony_ci 166bf215546Sopenharmony_ci # Append the changes to the .pickstatus.json file 167bf215546Sopenharmony_ci ui.save() 168bf215546Sopenharmony_ci v = await commit_state(amend=True) 169bf215546Sopenharmony_ci return (v, '') 170bf215546Sopenharmony_ci 171bf215546Sopenharmony_ci async def abort_cherry(self, ui: 'UI', err: str) -> None: 172bf215546Sopenharmony_ci await ui.feedback(f'{self.sha} ({self.description}) failed to apply\n{err}') 173bf215546Sopenharmony_ci async with COMMIT_LOCK: 174bf215546Sopenharmony_ci p = await asyncio.create_subprocess_exec( 175bf215546Sopenharmony_ci 'git', 'cherry-pick', '--abort', 176bf215546Sopenharmony_ci stdout=asyncio.subprocess.DEVNULL, 177bf215546Sopenharmony_ci stderr=asyncio.subprocess.DEVNULL, 178bf215546Sopenharmony_ci ) 179bf215546Sopenharmony_ci r = await p.wait() 180bf215546Sopenharmony_ci await ui.feedback(f'{"Successfully" if r == 0 else "Failed to"} abort cherry-pick.') 181bf215546Sopenharmony_ci 182bf215546Sopenharmony_ci async def denominate(self, ui: 'UI') -> bool: 183bf215546Sopenharmony_ci self.resolution = Resolution.DENOMINATED 184bf215546Sopenharmony_ci ui.save() 185bf215546Sopenharmony_ci v = await commit_state(message=f'Mark {self.sha} as denominated') 186bf215546Sopenharmony_ci assert v 187bf215546Sopenharmony_ci await ui.feedback(f'{self.sha} ({self.description}) denominated successfully') 188bf215546Sopenharmony_ci return True 189bf215546Sopenharmony_ci 190bf215546Sopenharmony_ci async def backport(self, ui: 'UI') -> bool: 191bf215546Sopenharmony_ci self.resolution = Resolution.BACKPORTED 192bf215546Sopenharmony_ci ui.save() 193bf215546Sopenharmony_ci v = await commit_state(message=f'Mark {self.sha} as backported') 194bf215546Sopenharmony_ci assert v 195bf215546Sopenharmony_ci await ui.feedback(f'{self.sha} ({self.description}) backported successfully') 196bf215546Sopenharmony_ci return True 197bf215546Sopenharmony_ci 198bf215546Sopenharmony_ci async def resolve(self, ui: 'UI') -> None: 199bf215546Sopenharmony_ci self.resolution = Resolution.MERGED 200bf215546Sopenharmony_ci ui.save() 201bf215546Sopenharmony_ci v = await commit_state(amend=True) 202bf215546Sopenharmony_ci assert v 203bf215546Sopenharmony_ci await ui.feedback(f'{self.sha} ({self.description}) committed successfully') 204bf215546Sopenharmony_ci 205bf215546Sopenharmony_ci 206bf215546Sopenharmony_ciasync def get_new_commits(sha: str) -> typing.List[typing.Tuple[str, str]]: 207bf215546Sopenharmony_ci # Try to get the authoritative upstream main 208bf215546Sopenharmony_ci p = await asyncio.create_subprocess_exec( 209bf215546Sopenharmony_ci 'git', 'for-each-ref', '--format=%(upstream)', 'refs/heads/main', 210bf215546Sopenharmony_ci stdout=asyncio.subprocess.PIPE, 211bf215546Sopenharmony_ci stderr=asyncio.subprocess.DEVNULL) 212bf215546Sopenharmony_ci out, _ = await p.communicate() 213bf215546Sopenharmony_ci upstream = out.decode().strip() 214bf215546Sopenharmony_ci 215bf215546Sopenharmony_ci p = await asyncio.create_subprocess_exec( 216bf215546Sopenharmony_ci 'git', 'log', '--pretty=oneline', f'{sha}..{upstream}', 217bf215546Sopenharmony_ci stdout=asyncio.subprocess.PIPE, 218bf215546Sopenharmony_ci stderr=asyncio.subprocess.DEVNULL) 219bf215546Sopenharmony_ci out, _ = await p.communicate() 220bf215546Sopenharmony_ci assert p.returncode == 0, f"git log didn't work: {sha}" 221bf215546Sopenharmony_ci return list(split_commit_list(out.decode().strip())) 222bf215546Sopenharmony_ci 223bf215546Sopenharmony_ci 224bf215546Sopenharmony_cidef split_commit_list(commits: str) -> typing.Generator[typing.Tuple[str, str], None, None]: 225bf215546Sopenharmony_ci if not commits: 226bf215546Sopenharmony_ci return 227bf215546Sopenharmony_ci for line in commits.split('\n'): 228bf215546Sopenharmony_ci v = tuple(line.split(' ', 1)) 229bf215546Sopenharmony_ci assert len(v) == 2, 'this is really just for mypy' 230bf215546Sopenharmony_ci yield typing.cast(typing.Tuple[str, str], v) 231bf215546Sopenharmony_ci 232bf215546Sopenharmony_ci 233bf215546Sopenharmony_ciasync def is_commit_in_branch(sha: str) -> bool: 234bf215546Sopenharmony_ci async with SEM: 235bf215546Sopenharmony_ci p = await asyncio.create_subprocess_exec( 236bf215546Sopenharmony_ci 'git', 'merge-base', '--is-ancestor', sha, 'HEAD', 237bf215546Sopenharmony_ci stdout=asyncio.subprocess.DEVNULL, 238bf215546Sopenharmony_ci stderr=asyncio.subprocess.DEVNULL, 239bf215546Sopenharmony_ci ) 240bf215546Sopenharmony_ci await p.wait() 241bf215546Sopenharmony_ci return p.returncode == 0 242bf215546Sopenharmony_ci 243bf215546Sopenharmony_ci 244bf215546Sopenharmony_ciasync def full_sha(sha: str) -> str: 245bf215546Sopenharmony_ci async with SEM: 246bf215546Sopenharmony_ci p = await asyncio.create_subprocess_exec( 247bf215546Sopenharmony_ci 'git', 'rev-parse', sha, 248bf215546Sopenharmony_ci stdout=asyncio.subprocess.PIPE, 249bf215546Sopenharmony_ci stderr=asyncio.subprocess.DEVNULL, 250bf215546Sopenharmony_ci ) 251bf215546Sopenharmony_ci out, _ = await p.communicate() 252bf215546Sopenharmony_ci if p.returncode: 253bf215546Sopenharmony_ci raise PickUIException(f'Invalid Sha {sha}') 254bf215546Sopenharmony_ci return out.decode().strip() 255bf215546Sopenharmony_ci 256bf215546Sopenharmony_ci 257bf215546Sopenharmony_ciasync def resolve_nomination(commit: 'Commit', version: str) -> 'Commit': 258bf215546Sopenharmony_ci async with SEM: 259bf215546Sopenharmony_ci p = await asyncio.create_subprocess_exec( 260bf215546Sopenharmony_ci 'git', 'log', '--format=%B', '-1', commit.sha, 261bf215546Sopenharmony_ci stdout=asyncio.subprocess.PIPE, 262bf215546Sopenharmony_ci stderr=asyncio.subprocess.DEVNULL, 263bf215546Sopenharmony_ci ) 264bf215546Sopenharmony_ci _out, _ = await p.communicate() 265bf215546Sopenharmony_ci assert p.returncode == 0, f'git log for {commit.sha} failed' 266bf215546Sopenharmony_ci out = _out.decode() 267bf215546Sopenharmony_ci 268bf215546Sopenharmony_ci # We give precedence to fixes and cc tags over revert tags. 269bf215546Sopenharmony_ci # XXX: not having the walrus operator available makes me sad := 270bf215546Sopenharmony_ci m = IS_FIX.search(out) 271bf215546Sopenharmony_ci if m: 272bf215546Sopenharmony_ci # We set the nomination_type and because_sha here so that we can later 273bf215546Sopenharmony_ci # check to see if this fixes another staged commit. 274bf215546Sopenharmony_ci try: 275bf215546Sopenharmony_ci commit.because_sha = fixed = await full_sha(m.group(1)) 276bf215546Sopenharmony_ci except PickUIException: 277bf215546Sopenharmony_ci pass 278bf215546Sopenharmony_ci else: 279bf215546Sopenharmony_ci commit.nomination_type = NominationType.FIXES 280bf215546Sopenharmony_ci if await is_commit_in_branch(fixed): 281bf215546Sopenharmony_ci commit.nominated = True 282bf215546Sopenharmony_ci return commit 283bf215546Sopenharmony_ci 284bf215546Sopenharmony_ci m = IS_CC.search(out) 285bf215546Sopenharmony_ci if m: 286bf215546Sopenharmony_ci if m.groups() == (None, None) or version in m.groups(): 287bf215546Sopenharmony_ci commit.nominated = True 288bf215546Sopenharmony_ci commit.nomination_type = NominationType.CC 289bf215546Sopenharmony_ci return commit 290bf215546Sopenharmony_ci 291bf215546Sopenharmony_ci m = IS_REVERT.search(out) 292bf215546Sopenharmony_ci if m: 293bf215546Sopenharmony_ci # See comment for IS_FIX path 294bf215546Sopenharmony_ci try: 295bf215546Sopenharmony_ci commit.because_sha = reverted = await full_sha(m.group(1)) 296bf215546Sopenharmony_ci except PickUIException: 297bf215546Sopenharmony_ci pass 298bf215546Sopenharmony_ci else: 299bf215546Sopenharmony_ci commit.nomination_type = NominationType.REVERT 300bf215546Sopenharmony_ci if await is_commit_in_branch(reverted): 301bf215546Sopenharmony_ci commit.nominated = True 302bf215546Sopenharmony_ci return commit 303bf215546Sopenharmony_ci 304bf215546Sopenharmony_ci return commit 305bf215546Sopenharmony_ci 306bf215546Sopenharmony_ci 307bf215546Sopenharmony_ciasync def resolve_fixes(commits: typing.List['Commit'], previous: typing.List['Commit']) -> None: 308bf215546Sopenharmony_ci """Determine if any of the undecided commits fix/revert a staged commit. 309bf215546Sopenharmony_ci 310bf215546Sopenharmony_ci The are still needed if they apply to a commit that is staged for 311bf215546Sopenharmony_ci inclusion, but not yet included. 312bf215546Sopenharmony_ci 313bf215546Sopenharmony_ci This must be done in order, because a commit 3 might fix commit 2 which 314bf215546Sopenharmony_ci fixes commit 1. 315bf215546Sopenharmony_ci """ 316bf215546Sopenharmony_ci shas: typing.Set[str] = set(c.sha for c in previous if c.nominated) 317bf215546Sopenharmony_ci assert None not in shas, 'None in shas' 318bf215546Sopenharmony_ci 319bf215546Sopenharmony_ci for commit in reversed(commits): 320bf215546Sopenharmony_ci if not commit.nominated and commit.nomination_type is NominationType.FIXES: 321bf215546Sopenharmony_ci commit.nominated = commit.because_sha in shas 322bf215546Sopenharmony_ci 323bf215546Sopenharmony_ci if commit.nominated: 324bf215546Sopenharmony_ci shas.add(commit.sha) 325bf215546Sopenharmony_ci 326bf215546Sopenharmony_ci for commit in commits: 327bf215546Sopenharmony_ci if (commit.nomination_type is NominationType.REVERT and 328bf215546Sopenharmony_ci commit.because_sha in shas): 329bf215546Sopenharmony_ci for oldc in reversed(commits): 330bf215546Sopenharmony_ci if oldc.sha == commit.because_sha: 331bf215546Sopenharmony_ci # In this case a commit that hasn't yet been applied is 332bf215546Sopenharmony_ci # reverted, we don't want to apply that commit at all 333bf215546Sopenharmony_ci oldc.nominated = False 334bf215546Sopenharmony_ci oldc.resolution = Resolution.DENOMINATED 335bf215546Sopenharmony_ci commit.nominated = False 336bf215546Sopenharmony_ci commit.resolution = Resolution.DENOMINATED 337bf215546Sopenharmony_ci shas.remove(commit.because_sha) 338bf215546Sopenharmony_ci break 339bf215546Sopenharmony_ci 340bf215546Sopenharmony_ci 341bf215546Sopenharmony_ciasync def gather_commits(version: str, previous: typing.List['Commit'], 342bf215546Sopenharmony_ci new: typing.List[typing.Tuple[str, str]], cb) -> typing.List['Commit']: 343bf215546Sopenharmony_ci # We create an array of the final size up front, then we pass that array 344bf215546Sopenharmony_ci # to the "inner" co-routine, which is turned into a list of tasks and 345bf215546Sopenharmony_ci # collected by asyncio.gather. We do this to allow the tasks to be 346bf215546Sopenharmony_ci # asynchronously gathered, but to also ensure that the commits list remains 347bf215546Sopenharmony_ci # in order. 348bf215546Sopenharmony_ci m_commits: typing.List[typing.Optional['Commit']] = [None] * len(new) 349bf215546Sopenharmony_ci tasks = [] 350bf215546Sopenharmony_ci 351bf215546Sopenharmony_ci async def inner(commit: 'Commit', version: str, 352bf215546Sopenharmony_ci commits: typing.List[typing.Optional['Commit']], 353bf215546Sopenharmony_ci index: int, cb) -> None: 354bf215546Sopenharmony_ci commits[index] = await resolve_nomination(commit, version) 355bf215546Sopenharmony_ci cb() 356bf215546Sopenharmony_ci 357bf215546Sopenharmony_ci for i, (sha, desc) in enumerate(new): 358bf215546Sopenharmony_ci tasks.append(asyncio.ensure_future( 359bf215546Sopenharmony_ci inner(Commit(sha, desc), version, m_commits, i, cb))) 360bf215546Sopenharmony_ci 361bf215546Sopenharmony_ci await asyncio.gather(*tasks) 362bf215546Sopenharmony_ci assert None not in m_commits 363bf215546Sopenharmony_ci commits = typing.cast(typing.List[Commit], m_commits) 364bf215546Sopenharmony_ci 365bf215546Sopenharmony_ci await resolve_fixes(commits, previous) 366bf215546Sopenharmony_ci 367bf215546Sopenharmony_ci for commit in commits: 368bf215546Sopenharmony_ci if commit.resolution is Resolution.UNRESOLVED and not commit.nominated: 369bf215546Sopenharmony_ci commit.resolution = Resolution.NOTNEEDED 370bf215546Sopenharmony_ci 371bf215546Sopenharmony_ci return commits 372bf215546Sopenharmony_ci 373bf215546Sopenharmony_ci 374bf215546Sopenharmony_cidef load() -> typing.List['Commit']: 375bf215546Sopenharmony_ci if not pick_status_json.exists(): 376bf215546Sopenharmony_ci return [] 377bf215546Sopenharmony_ci with pick_status_json.open('r') as f: 378bf215546Sopenharmony_ci raw = json.load(f) 379bf215546Sopenharmony_ci return [Commit.from_json(c) for c in raw] 380bf215546Sopenharmony_ci 381bf215546Sopenharmony_ci 382bf215546Sopenharmony_cidef save(commits: typing.Iterable['Commit']) -> None: 383bf215546Sopenharmony_ci commits = list(commits) 384bf215546Sopenharmony_ci with pick_status_json.open('wt') as f: 385bf215546Sopenharmony_ci json.dump([c.to_json() for c in commits], f, indent=4) 386bf215546Sopenharmony_ci 387bf215546Sopenharmony_ci asyncio.ensure_future(commit_state(message=f'Update to {commits[0].sha}')) 388