• 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 git_utils
18# pylint: disable=import-error
19import metadata_pb2  # type: ignore
20import updater_utils
21
22
23class GitUpdater(base_updater.Updater):
24    """Updater for Git upstream."""
25    UPSTREAM_REMOTE_NAME: str = "update_origin"
26
27    def is_supported_url(self) -> bool:
28        return git_utils.is_valid_url(self._proj_path, self._old_url.value)
29
30    @staticmethod
31    def _is_likely_android_remote(url: str) -> bool:
32        """Returns True if the URL is likely to be the project's Android remote."""
33        # There isn't a strict rule for finding the correct remote for upstream-master,
34        # so we have to guess. Be careful to filter out things that look almost right
35        # but aren't. Here's an example of a project that has a lot of false positives:
36        #
37        # aosp    /usr/local/google/home/danalbert/src/mirrors/android/refs/aosp/toolchain/rr.git (fetch)
38        # aosp    persistent-https://android.git.corp.google.com/toolchain/rr (push)
39        # origin  https://github.com/DanAlbert/rr.git (fetch)
40        # origin  https://github.com/DanAlbert/rr.git (push)
41        # unmirrored      persistent-https://android.git.corp.google.com/toolchain/rr (fetch)
42        # unmirrored      persistent-https://android.git.corp.google.com/toolchain/rr (push)
43        # update_origin   https://github.com/rr-debugger/rr (fetch)
44        # update_origin   https://github.com/rr-debugger/rr (push)
45        # upstream        https://github.com/rr-debugger/rr.git (fetch)
46        # upstream        https://github.com/rr-debugger/rr.git (push)
47        #
48        # unmirrored is the correct remote here. It's not a local path, and contains
49        # either /platform/external/ or /toolchain/ (the two common roots for third-
50        # party Android imports).
51        if '://' not in url:
52            # Skip anything that's likely a local GoB mirror.
53            return False
54        if '/platform/external/' in url:
55            return True
56        if '/toolchain/' in url:
57            return True
58        return False
59
60    def _setup_remote(self) -> None:
61        remotes = git_utils.list_remotes(self._proj_path)
62        current_remote_url = None
63        android_remote_name: str | None = None
64        for name, url in remotes.items():
65            if name == self.UPSTREAM_REMOTE_NAME:
66                current_remote_url = url
67
68            if self._is_likely_android_remote(url):
69                android_remote_name = name
70
71        if android_remote_name is None:
72            remotes_formatted = "\n".join(f"{k} {v}" for k, v in remotes.items())
73            raise RuntimeError(
74                f"Could not determine android remote for {self._proj_path}. Tried:\n"
75                f"{remotes_formatted}")
76
77        if current_remote_url is not None and current_remote_url != self._old_url.value:
78            git_utils.remove_remote(self._proj_path, self.UPSTREAM_REMOTE_NAME)
79            current_remote_url = None
80
81        if current_remote_url is None:
82            git_utils.add_remote(self._proj_path, self.UPSTREAM_REMOTE_NAME,
83                                 self._old_url.value)
84
85        git_utils.fetch(self._proj_path,
86                        [self.UPSTREAM_REMOTE_NAME, android_remote_name])
87
88    def check(self) -> None:
89        """Checks upstream and returns whether a new version is available."""
90        self._setup_remote()
91        if git_utils.is_commit(self._old_ver):
92            # Update to remote head.
93            self._check_head()
94        else:
95            # Update to latest version tag.
96            self._check_tag()
97
98    def _check_tag(self) -> None:
99        tags = git_utils.list_remote_tags(self._proj_path,
100                                          self.UPSTREAM_REMOTE_NAME)
101        self._new_ver = updater_utils.get_latest_version(self._old_ver, tags)
102
103    def _check_head(self) -> None:
104        branch = git_utils.detect_default_branch(self._proj_path,
105                                                 self.UPSTREAM_REMOTE_NAME)
106        self._new_ver = git_utils.get_sha_for_branch(
107            self._proj_path, self.UPSTREAM_REMOTE_NAME + '/' + branch)
108
109    def update(self, skip_post_update: bool) -> None:
110        """Updates the package.
111        Has to call check() before this function.
112        """
113        print(f"Running `git merge {self._new_ver}`...")
114        git_utils.merge(self._proj_path, self._new_ver)
115        if not skip_post_update:
116            updater_utils.run_post_update(self._proj_path, self._proj_path)