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