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 color import Color, color_string 22from manifest import Manifest 23import metadata_pb2 # type: ignore 24 25 26class GitUpdater(base_updater.Updater): 27 """Updater for Git upstream.""" 28 UPSTREAM_REMOTE_NAME: str = "update_origin" 29 30 def is_supported_url(self) -> bool: 31 return git_utils.is_valid_url(self._proj_path, self._old_identifier.value) 32 33 def setup_remote(self) -> None: 34 remotes = git_utils.list_remotes(self._proj_path) 35 current_remote_url = None 36 for name, url in remotes.items(): 37 if name == self.UPSTREAM_REMOTE_NAME: 38 current_remote_url = url 39 40 if current_remote_url is not None and current_remote_url != self._old_identifier.value: 41 git_utils.remove_remote(self._proj_path, self.UPSTREAM_REMOTE_NAME) 42 current_remote_url = None 43 44 if current_remote_url is None: 45 git_utils.add_remote(self._proj_path, self.UPSTREAM_REMOTE_NAME, 46 self._old_identifier.value) 47 48 git_utils.fetch(self._proj_path, self.UPSTREAM_REMOTE_NAME) 49 50 def set_custom_version(self, custom_version: str) -> None: 51 super().set_custom_version(custom_version) 52 if not git_utils.list_branches_with_commit(self._proj_path, custom_version, self.UPSTREAM_REMOTE_NAME): 53 raise RuntimeError( 54 f"Can not upgrade to {custom_version}. This version does not belong to any branches.") 55 56 def set_new_versions_for_commit(self, latest_sha: str, latest_tag: str | None = None) -> None: 57 self._new_identifier.version = latest_sha 58 if latest_tag is not None and git_utils.is_ancestor( 59 self._proj_path, self._old_identifier.version, latest_tag): 60 self._alternative_new_ver = latest_tag 61 62 def set_new_versions_for_tag(self, latest_sha: str, latest_tag: str | None = None) -> None: 63 if latest_tag is None: 64 project = fileutils.canonicalize_project_path(self.project_path) 65 print(color_string( 66 f"{project} is currently tracking upstream tags but either no " 67 "tags were found in the upstream repository or the tag does not " 68 "belong to any branch. No latest tag available", Color.STALE 69 )) 70 self._new_identifier.ClearField("version") 71 self._alternative_new_ver = latest_sha 72 return 73 self._new_identifier.version = latest_tag 74 if git_utils.is_ancestor( 75 self._proj_path, self._old_identifier.version, latest_sha): 76 self._alternative_new_ver = latest_sha 77 78 def check(self) -> None: 79 """Checks upstream and returns whether a new version is available.""" 80 self.setup_remote() 81 82 latest_sha = self.current_head_of_upstream_default_branch() 83 latest_tag = self.latest_tag_of_upstream() 84 85 if git_utils.is_commit(self._old_identifier.version): 86 self.set_new_versions_for_commit(latest_sha, latest_tag) 87 else: 88 self.set_new_versions_for_tag(latest_sha, latest_tag) 89 90 def latest_tag_of_upstream(self) -> str | None: 91 tags = git_utils.list_remote_tags(self._proj_path, self.UPSTREAM_REMOTE_NAME) 92 if not tags: 93 return None 94 95 parsed_tags = [updater_utils.parse_remote_tag(tag) for tag in tags] 96 tag = updater_utils.get_latest_stable_release_tag(self._old_identifier.version, parsed_tags) 97 if not git_utils.list_branches_with_commit(self._proj_path, tag, self.UPSTREAM_REMOTE_NAME): 98 return None 99 100 return tag 101 102 def current_head_of_upstream_default_branch(self) -> str: 103 branch = git_utils.detect_default_branch(self._proj_path, 104 self.UPSTREAM_REMOTE_NAME) 105 return git_utils.get_sha_for_branch( 106 self._proj_path, self.UPSTREAM_REMOTE_NAME + '/' + branch) 107 108 def update(self) -> None: 109 """Updates the package. 110 Has to call check() before this function. 111 """ 112 print(f"Running `git merge {self._new_identifier.version}`...") 113 git_utils.merge(self._proj_path, self._new_identifier.version) 114 115 def _determine_android_fetch_ref(self) -> str: 116 """Returns the ref that should be fetched from the android remote.""" 117 # It isn't particularly efficient to reparse the tree for every 118 # project, but we don't guarantee that all paths passed to updater.sh 119 # are actually in the same tree so it wouldn't necessarily be correct 120 # to do this once at the top level. This isn't the slow part anyway, 121 # so it can be dealt with if that ever changes. 122 root = fileutils.find_tree_containing(self._proj_path) 123 manifest = Manifest.for_tree(root) 124 manifest_path = str(self._proj_path.relative_to(root)) 125 try: 126 project = manifest.project_with_path(manifest_path) 127 except KeyError as ex: 128 raise RuntimeError( 129 f"Did not find {manifest_path} in {manifest.path} (tree root is {root})" 130 ) from ex 131 return project.revision 132