1# Copyright (C) 2018 The Android Open Source Project 2# 3# Licensed under the Apache License, Version 2.0 (the "License"); 4# you may not use this file except in compliance with the License. 5# You may obtain a copy of the License at 6# 7# http://www.apache.org/licenses/LICENSE-2.0 8# 9# Unless required by applicable law or agreed to in writing, software 10# distributed under the License is distributed on an "AS IS" BASIS, 11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12# See the License for the specific language governing permissions and 13# limitations under the License. 14"""Module to check updates from Git upstream.""" 15 16import base_updater 17import fileutils 18import git_utils 19import updater_utils 20# pylint: disable=import-error 21from manifest import Manifest 22 23 24class GitUpdater(base_updater.Updater): 25 """Updater for Git upstream.""" 26 UPSTREAM_REMOTE_NAME: str = "update_origin" 27 28 def is_supported_url(self) -> bool: 29 return git_utils.is_valid_url(self._proj_path, self._old_identifier.value) 30 31 def setup_remote(self) -> None: 32 remotes = git_utils.list_remotes(self._proj_path) 33 current_remote_url = None 34 for name, url in remotes.items(): 35 if name == self.UPSTREAM_REMOTE_NAME: 36 current_remote_url = url 37 38 if current_remote_url is not None and current_remote_url != self._old_identifier.value: 39 git_utils.remove_remote(self._proj_path, self.UPSTREAM_REMOTE_NAME) 40 current_remote_url = None 41 42 if current_remote_url is None: 43 git_utils.add_remote(self._proj_path, self.UPSTREAM_REMOTE_NAME, 44 self._old_identifier.value) 45 46 git_utils.fetch(self._proj_path, self.UPSTREAM_REMOTE_NAME) 47 48 def check(self) -> None: 49 """Checks upstream and returns whether a new version is available.""" 50 self.setup_remote() 51 possible_alternative_new_ver: str | None = None 52 if git_utils.is_commit(self._old_identifier.version): 53 # Update to remote head. 54 self._new_identifier.version = self.current_head_of_upstream_default_branch() 55 # Some libraries don't have a tag. We only populate 56 # _alternative_new_ver if there is a tag newer than _old_ver. 57 # Checks if there is a tag newer than AOSP's SHA 58 if (tag := self.latest_tag_of_upstream()) is not None: 59 possible_alternative_new_ver = tag 60 else: 61 # Update to the latest version tag. 62 tag = self.latest_tag_of_upstream() 63 if tag is None: 64 project = fileutils.canonicalize_project_path(self.project_path) 65 raise RuntimeError( 66 f"{project} is currently tracking upstream tags but no tags were " 67 "found in the upstream repository" 68 ) 69 self._new_identifier.version = tag 70 # Checks if there is a SHA newer than AOSP's tag 71 possible_alternative_new_ver = self.current_head_of_upstream_default_branch() 72 if possible_alternative_new_ver is not None and git_utils.is_ancestor( 73 self._proj_path, 74 self._old_identifier.version, 75 possible_alternative_new_ver 76 ): 77 self._alternative_new_ver = possible_alternative_new_ver 78 79 def latest_tag_of_upstream(self) -> str | None: 80 tags = git_utils.list_remote_tags(self._proj_path, self.UPSTREAM_REMOTE_NAME) 81 if not tags: 82 return None 83 84 parsed_tags = [updater_utils.parse_remote_tag(tag) for tag in tags] 85 tag = updater_utils.get_latest_stable_release_tag(self._old_identifier.version, parsed_tags) 86 return tag 87 88 def current_head_of_upstream_default_branch(self) -> str: 89 branch = git_utils.detect_default_branch(self._proj_path, 90 self.UPSTREAM_REMOTE_NAME) 91 return git_utils.get_sha_for_branch( 92 self._proj_path, self.UPSTREAM_REMOTE_NAME + '/' + branch) 93 94 def update(self) -> None: 95 """Updates the package. 96 Has to call check() before this function. 97 """ 98 print(f"Running `git merge {self._new_identifier.version}`...") 99 git_utils.merge(self._proj_path, self._new_identifier.version) 100 101 def _determine_android_fetch_ref(self) -> str: 102 """Returns the ref that should be fetched from the android remote.""" 103 # It isn't particularly efficient to reparse the tree for every 104 # project, but we don't guarantee that all paths passed to updater.sh 105 # are actually in the same tree so it wouldn't necessarily be correct 106 # to do this once at the top level. This isn't the slow part anyway, 107 # so it can be dealt with if that ever changes. 108 root = fileutils.find_tree_containing(self._proj_path) 109 manifest = Manifest.for_tree(root) 110 manifest_path = str(self._proj_path.relative_to(root)) 111 try: 112 project = manifest.project_with_path(manifest_path) 113 except KeyError as ex: 114 raise RuntimeError( 115 f"Did not find {manifest_path} in {manifest.path} (tree root is {root})" 116 ) from ex 117 return project.revision 118