• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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