xref: /aosp_15_r20/external/wayland-protocols/import_snapshot.py (revision 6c119a463dd5c45dd05bbe67429293292dde15ee)
1*6c119a46SAndroid Build Coastguard Worker#!/usr/bin/python3
2*6c119a46SAndroid Build Coastguard Worker# Copyright 2024 The Android Open Source Project
3*6c119a46SAndroid Build Coastguard Worker#
4*6c119a46SAndroid Build Coastguard Worker# Licensed under the Apache License, Version 2.0 (the "License");
5*6c119a46SAndroid Build Coastguard Worker# you may not use this file except in compliance with the License.
6*6c119a46SAndroid Build Coastguard Worker# You may obtain a copy of the License at
7*6c119a46SAndroid Build Coastguard Worker#
8*6c119a46SAndroid Build Coastguard Worker#      http://www.apache.org/licenses/LICENSE-2.0
9*6c119a46SAndroid Build Coastguard Worker#
10*6c119a46SAndroid Build Coastguard Worker# Unless required by applicable law or agreed to in writing, software
11*6c119a46SAndroid Build Coastguard Worker# distributed under the License is distributed on an "AS IS" BASIS,
12*6c119a46SAndroid Build Coastguard Worker# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13*6c119a46SAndroid Build Coastguard Worker# See the License for the specific language governing permissions and
14*6c119a46SAndroid Build Coastguard Worker# limitations under the License.
15*6c119a46SAndroid Build Coastguard Worker
16*6c119a46SAndroid Build Coastguard Workerimport argparse
17*6c119a46SAndroid Build Coastguard Workerimport datetime
18*6c119a46SAndroid Build Coastguard Workerimport logging
19*6c119a46SAndroid Build Coastguard Workerimport pathlib
20*6c119a46SAndroid Build Coastguard Workerimport re
21*6c119a46SAndroid Build Coastguard Workerimport shutil
22*6c119a46SAndroid Build Coastguard Workerimport subprocess
23*6c119a46SAndroid Build Coastguard Workerimport sys
24*6c119a46SAndroid Build Coastguard Worker
25*6c119a46SAndroid Build Coastguard WorkerDESCRIPTION = (
26*6c119a46SAndroid Build Coastguard Worker    'Helper script for importing a snapshot from upstream Wayland protocol '
27*6c119a46SAndroid Build Coastguard Worker    'sources.')
28*6c119a46SAndroid Build Coastguard Worker
29*6c119a46SAndroid Build Coastguard WorkerINTENDED_USAGE = ('''
30*6c119a46SAndroid Build Coastguard WorkerIntended Usage:
31*6c119a46SAndroid Build Coastguard Worker    # Update the freedesktop.org subdirectory to version 1.32
32*6c119a46SAndroid Build Coastguard Worker    # Check https://gitlab.freedesktop.org/wayland/wayland-protocols/-/tags
33*6c119a46SAndroid Build Coastguard Worker    # for valid version tags.
34*6c119a46SAndroid Build Coastguard Worker    ./import_snapshot.py freedesktop.org 1.32
35*6c119a46SAndroid Build Coastguard Worker
36*6c119a46SAndroid Build Coastguard Worker    # Update the chromium.org subdirectory to the latest
37*6c119a46SAndroid Build Coastguard Worker    ./import_snapshot.py chromium.org main
38*6c119a46SAndroid Build Coastguard Worker''')
39*6c119a46SAndroid Build Coastguard Worker
40*6c119a46SAndroid Build Coastguard Worker
41*6c119a46SAndroid Build Coastguard Workerclass GitRepo:
42*6c119a46SAndroid Build Coastguard Worker    """Issues git commands against a local checkout located at some path."""
43*6c119a46SAndroid Build Coastguard Worker
44*6c119a46SAndroid Build Coastguard Worker    def __init__(self, base: pathlib.PurePath):
45*6c119a46SAndroid Build Coastguard Worker        logging.debug("GitRepo base %s", base)
46*6c119a46SAndroid Build Coastguard Worker        self._base = base
47*6c119a46SAndroid Build Coastguard Worker
48*6c119a46SAndroid Build Coastguard Worker    @property
49*6c119a46SAndroid Build Coastguard Worker    def base(self) -> pathlib.PurePath:
50*6c119a46SAndroid Build Coastguard Worker        """Gets the base path used the repo."""
51*6c119a46SAndroid Build Coastguard Worker        return self._base
52*6c119a46SAndroid Build Coastguard Worker
53*6c119a46SAndroid Build Coastguard Worker    def _git(self,
54*6c119a46SAndroid Build Coastguard Worker             cmd: list[str],
55*6c119a46SAndroid Build Coastguard Worker             capture_output: bool = True,
56*6c119a46SAndroid Build Coastguard Worker             check: bool = True) -> subprocess.CompletedProcess:
57*6c119a46SAndroid Build Coastguard Worker        return subprocess.run(['git', '-C', self._base] + cmd,
58*6c119a46SAndroid Build Coastguard Worker                              capture_output=capture_output,
59*6c119a46SAndroid Build Coastguard Worker                              check=check,
60*6c119a46SAndroid Build Coastguard Worker                              text=True)
61*6c119a46SAndroid Build Coastguard Worker
62*6c119a46SAndroid Build Coastguard Worker    def get_hash_for_version(self, version) -> str:
63*6c119a46SAndroid Build Coastguard Worker        """Gets the hash associated with a |version| tag or branch."""
64*6c119a46SAndroid Build Coastguard Worker        logging.debug("GitRepo.get_hash_for_version version %s", version)
65*6c119a46SAndroid Build Coastguard Worker        return self._git(['show-ref', '--hash',
66*6c119a46SAndroid Build Coastguard Worker                          version]).stdout.splitlines()[0].strip()
67*6c119a46SAndroid Build Coastguard Worker
68*6c119a46SAndroid Build Coastguard Worker    def git_ref_name_for_version(self, version) -> str | None:
69*6c119a46SAndroid Build Coastguard Worker        """Gets the named ref corresponding to |version|, if one exists."""
70*6c119a46SAndroid Build Coastguard Worker        logging.debug("GitRepo.get_ref_name_for_version version %s", version)
71*6c119a46SAndroid Build Coastguard Worker        ref = self._git(['describe', '--all', '--exact-match', version],
72*6c119a46SAndroid Build Coastguard Worker                        check=False).stdout.splitlines()[0].strip()
73*6c119a46SAndroid Build Coastguard Worker        if ref.startswith('tags/'):
74*6c119a46SAndroid Build Coastguard Worker            return ref.removeprefix('tags/')
75*6c119a46SAndroid Build Coastguard Worker        if ref.startswith('heads/'):
76*6c119a46SAndroid Build Coastguard Worker            return ref.removeprefix('heads/')
77*6c119a46SAndroid Build Coastguard Worker        return None
78*6c119a46SAndroid Build Coastguard Worker
79*6c119a46SAndroid Build Coastguard Worker    def get_files(self, version: str,
80*6c119a46SAndroid Build Coastguard Worker                  paths: list[pathlib.PurePath]) -> list[pathlib.Path]:
81*6c119a46SAndroid Build Coastguard Worker        """Gets the list of files under |paths| that are part of the Git tree at |version|."""
82*6c119a46SAndroid Build Coastguard Worker        logging.debug("GitRepo.get_files version %s paths %s", version, paths)
83*6c119a46SAndroid Build Coastguard Worker        stdout = self._git(
84*6c119a46SAndroid Build Coastguard Worker            ['ls-tree', '-r', '--name-only', f'{version}^{{tree}}'] +
85*6c119a46SAndroid Build Coastguard Worker            paths).stdout
86*6c119a46SAndroid Build Coastguard Worker        return list(pathlib.PurePath(path) for path in stdout.splitlines())
87*6c119a46SAndroid Build Coastguard Worker
88*6c119a46SAndroid Build Coastguard Worker    def assert_no_uncommitted_changes(self) -> None:
89*6c119a46SAndroid Build Coastguard Worker        """Asserts that the repo has no uncommited changes."""
90*6c119a46SAndroid Build Coastguard Worker        r = self._git(['diff-files', '--quiet', '--ignore-submodules'],
91*6c119a46SAndroid Build Coastguard Worker                      check=False)
92*6c119a46SAndroid Build Coastguard Worker        if r.returncode:
93*6c119a46SAndroid Build Coastguard Worker            sys.exit('Error: Your tree is dirty')
94*6c119a46SAndroid Build Coastguard Worker
95*6c119a46SAndroid Build Coastguard Worker        r = self._git([
96*6c119a46SAndroid Build Coastguard Worker            'diff-index', '--quiet', '--ignore-submodules', '--cached', 'HEAD'
97*6c119a46SAndroid Build Coastguard Worker        ],
98*6c119a46SAndroid Build Coastguard Worker                      check=False)
99*6c119a46SAndroid Build Coastguard Worker        if r.returncode:
100*6c119a46SAndroid Build Coastguard Worker            sys.exit('Error: You have staged changes')
101*6c119a46SAndroid Build Coastguard Worker
102*6c119a46SAndroid Build Coastguard Worker    def sparse_depth1_clone(self,
103*6c119a46SAndroid Build Coastguard Worker                            url: str,
104*6c119a46SAndroid Build Coastguard Worker                            version: str | None,
105*6c119a46SAndroid Build Coastguard Worker                            paths: list[str],
106*6c119a46SAndroid Build Coastguard Worker                            force_clean: bool = True) -> None:
107*6c119a46SAndroid Build Coastguard Worker        """Performs a sparse clone with depth=1 of a repo.
108*6c119a46SAndroid Build Coastguard Worker
109*6c119a46SAndroid Build Coastguard Worker        A sparse clone limits the clone to a particular set of files, and not
110*6c119a46SAndroid Build Coastguard Worker        all the files available in the repo.
111*6c119a46SAndroid Build Coastguard Worker
112*6c119a46SAndroid Build Coastguard Worker        A depth=1 clone fetches only the most recent version of each file
113*6c119a46SAndroid Build Coastguard Worker        cloned, and not the entire history.
114*6c119a46SAndroid Build Coastguard Worker
115*6c119a46SAndroid Build Coastguard Worker        Together that makes the checkout be faster and take up less space on
116*6c119a46SAndroid Build Coastguard Worker        disk, which is important for large repositories like the Chromium
117*6c119a46SAndroid Build Coastguard Worker        source tree.
118*6c119a46SAndroid Build Coastguard Worker
119*6c119a46SAndroid Build Coastguard Worker        |url| gives the url to the remote repository to clone.
120*6c119a46SAndroid Build Coastguard Worker
121*6c119a46SAndroid Build Coastguard Worker        |version| gives the version to clone. If not specified, 'HEAD' is assumed.
122*6c119a46SAndroid Build Coastguard Worker
123*6c119a46SAndroid Build Coastguard Worker        Paths in |paths| are included in the sparse checkout, which also means
124*6c119a46SAndroid Build Coastguard Worker        all files in the parents directories leading up to those directories are
125*6c119a46SAndroid Build Coastguard Worker        included. if |paths| is an empty list, all files at the root of the
126*6c119a46SAndroid Build Coastguard Worker        repository will be included.
127*6c119a46SAndroid Build Coastguard Worker
128*6c119a46SAndroid Build Coastguard Worker        |force_clean| ensures any existing checkout at |base| is removed.
129*6c119a46SAndroid Build Coastguard Worker        Setting this to False speeds up testing changes to the script when
130*6c119a46SAndroid Build Coastguard Worker        syncing a particular version, as it will only be cloned the first
131*6c119a46SAndroid Build Coastguard Worker        time.
132*6c119a46SAndroid Build Coastguard Worker        """
133*6c119a46SAndroid Build Coastguard Worker        logging.debug(
134*6c119a46SAndroid Build Coastguard Worker            "GitRepo.sparse_depth1_clone url %s version %s paths %s force_clean %s",
135*6c119a46SAndroid Build Coastguard Worker            url, version, paths, force_clean)
136*6c119a46SAndroid Build Coastguard Worker        self._base.parent.mkdir(parents=True, exist_ok=True)
137*6c119a46SAndroid Build Coastguard Worker        if force_clean and self._base.exists():
138*6c119a46SAndroid Build Coastguard Worker            shutil.rmtree(self._base)
139*6c119a46SAndroid Build Coastguard Worker
140*6c119a46SAndroid Build Coastguard Worker        if not self._base.exists():
141*6c119a46SAndroid Build Coastguard Worker            cmd = ['git', 'clone', '--filter=blob:none', '--depth=1']
142*6c119a46SAndroid Build Coastguard Worker            if paths:
143*6c119a46SAndroid Build Coastguard Worker                cmd.extend(['--sparse'])
144*6c119a46SAndroid Build Coastguard Worker            if version is not None and version != 'HEAD':
145*6c119a46SAndroid Build Coastguard Worker                cmd.extend(['-b', version])
146*6c119a46SAndroid Build Coastguard Worker            cmd.extend([url, self._base])
147*6c119a46SAndroid Build Coastguard Worker
148*6c119a46SAndroid Build Coastguard Worker            subprocess.run(cmd, capture_output=False, check=True, text=True)
149*6c119a46SAndroid Build Coastguard Worker
150*6c119a46SAndroid Build Coastguard Worker            if paths:
151*6c119a46SAndroid Build Coastguard Worker                self._git(['sparse-checkout', 'add'] + paths)
152*6c119a46SAndroid Build Coastguard Worker
153*6c119a46SAndroid Build Coastguard Worker    def add(self, path: pathlib.Path) -> None:
154*6c119a46SAndroid Build Coastguard Worker        """Stages a local file |path| in the index."""
155*6c119a46SAndroid Build Coastguard Worker        logging.debug("GitRepo.add path %s", path)
156*6c119a46SAndroid Build Coastguard Worker        self._git(['add', path])
157*6c119a46SAndroid Build Coastguard Worker
158*6c119a46SAndroid Build Coastguard Worker    def commit(self,
159*6c119a46SAndroid Build Coastguard Worker               message: str,
160*6c119a46SAndroid Build Coastguard Worker               allow_empty: bool = False,
161*6c119a46SAndroid Build Coastguard Worker               auto_add: bool = True) -> None:
162*6c119a46SAndroid Build Coastguard Worker        """Commits stages changed using |message|.
163*6c119a46SAndroid Build Coastguard Worker
164*6c119a46SAndroid Build Coastguard Worker        If |allow_empty| is true, an empty commit is allowed.
165*6c119a46SAndroid Build Coastguard Worker        If |auto_add| is true, changed files are added automatically.
166*6c119a46SAndroid Build Coastguard Worker        """
167*6c119a46SAndroid Build Coastguard Worker        logging.debug("GitRepo.commit message %s allow_empty %s auto_add %s",
168*6c119a46SAndroid Build Coastguard Worker                      message, allow_empty, auto_add)
169*6c119a46SAndroid Build Coastguard Worker        cmd = ['commit', '-m', message]
170*6c119a46SAndroid Build Coastguard Worker        if allow_empty:
171*6c119a46SAndroid Build Coastguard Worker            cmd.extend(['--allow-empty'])
172*6c119a46SAndroid Build Coastguard Worker        if auto_add:
173*6c119a46SAndroid Build Coastguard Worker            cmd.extend(['-a'])
174*6c119a46SAndroid Build Coastguard Worker
175*6c119a46SAndroid Build Coastguard Worker        self._git(cmd, capture_output=False)
176*6c119a46SAndroid Build Coastguard Worker
177*6c119a46SAndroid Build Coastguard Worker
178*6c119a46SAndroid Build Coastguard Workerclass AndroidMetadata:
179*6c119a46SAndroid Build Coastguard Worker    """Minimal set of functions for reading and updating METADATA files.
180*6c119a46SAndroid Build Coastguard Worker
181*6c119a46SAndroid Build Coastguard Worker    Officially these files are meant to be read and written using code
182*6c119a46SAndroid Build Coastguard Worker    generated from
183*6c119a46SAndroid Build Coastguard Worker    //build/soong/compliance/project_metadata_proto/project_metadata.proto,
184*6c119a46SAndroid Build Coastguard Worker    but using it would require adding a dependency on Python protocol buffer
185*6c119a46SAndroid Build Coastguard Worker    libraries as well as the generated code for the .proto file.
186*6c119a46SAndroid Build Coastguard Worker
187*6c119a46SAndroid Build Coastguard Worker    Instead we use the Python regex library module to parse and rewrite the
188*6c119a46SAndroid Build Coastguard Worker    metadata, as we don't need to do anything really complicated.
189*6c119a46SAndroid Build Coastguard Worker    """
190*6c119a46SAndroid Build Coastguard Worker
191*6c119a46SAndroid Build Coastguard Worker    def __init__(self, metadata_path: pathlib.Path):
192*6c119a46SAndroid Build Coastguard Worker        assert metadata_path.exists()
193*6c119a46SAndroid Build Coastguard Worker        self._metadata_path: pathlib.Path = metadata_path
194*6c119a46SAndroid Build Coastguard Worker        self._content: str | None = None
195*6c119a46SAndroid Build Coastguard Worker        self._url: str | None = None
196*6c119a46SAndroid Build Coastguard Worker        self._paths: list[pathlib.PurePath] | None = None
197*6c119a46SAndroid Build Coastguard Worker
198*6c119a46SAndroid Build Coastguard Worker    def _read_content(self) -> None:
199*6c119a46SAndroid Build Coastguard Worker        if self._content is None:
200*6c119a46SAndroid Build Coastguard Worker            with open(self._metadata_path, 'rt') as metadata_file:
201*6c119a46SAndroid Build Coastguard Worker                self._content = metadata_file.read()
202*6c119a46SAndroid Build Coastguard Worker
203*6c119a46SAndroid Build Coastguard Worker    def _write_content(self) -> None:
204*6c119a46SAndroid Build Coastguard Worker        if self._content is not None:
205*6c119a46SAndroid Build Coastguard Worker            with open(self._metadata_path, 'wt') as metadata_file:
206*6c119a46SAndroid Build Coastguard Worker                metadata_file.write(self._content)
207*6c119a46SAndroid Build Coastguard Worker
208*6c119a46SAndroid Build Coastguard Worker    def _read_raw_git_urls(self) -> None:
209*6c119a46SAndroid Build Coastguard Worker        if self._url is None:
210*6c119a46SAndroid Build Coastguard Worker            self._read_content()
211*6c119a46SAndroid Build Coastguard Worker
212*6c119a46SAndroid Build Coastguard Worker            paths = []
213*6c119a46SAndroid Build Coastguard Worker            URL_PATTERN = r'url\s*{\s*type:\s*GIT\s*value:\s*"([^"]*)"\s*}'
214*6c119a46SAndroid Build Coastguard Worker            for url in re.findall(URL_PATTERN, self._content):
215*6c119a46SAndroid Build Coastguard Worker                base_url = url
216*6c119a46SAndroid Build Coastguard Worker                path = None
217*6c119a46SAndroid Build Coastguard Worker
218*6c119a46SAndroid Build Coastguard Worker                if '/-/tree/' in url:
219*6c119a46SAndroid Build Coastguard Worker                    base_url, path = url.split('/-/tree/')
220*6c119a46SAndroid Build Coastguard Worker                    _, path = path.split('/', 1)
221*6c119a46SAndroid Build Coastguard Worker                elif '/+/' in url:
222*6c119a46SAndroid Build Coastguard Worker                    base_url, path = url.split('/+/')
223*6c119a46SAndroid Build Coastguard Worker                    _, path = path.split('/', 1)
224*6c119a46SAndroid Build Coastguard Worker
225*6c119a46SAndroid Build Coastguard Worker                if self._url and self._url != base_url:
226*6c119a46SAndroid Build Coastguard Worker                    sys.exit(
227*6c119a46SAndroid Build Coastguard Worker                        f'Error: Inconsistent git URLs in {self._metadata_path} ({self._url} vs {base_url})'
228*6c119a46SAndroid Build Coastguard Worker                    )
229*6c119a46SAndroid Build Coastguard Worker
230*6c119a46SAndroid Build Coastguard Worker                self._url = base_url
231*6c119a46SAndroid Build Coastguard Worker                if path:
232*6c119a46SAndroid Build Coastguard Worker                    paths.append(path)
233*6c119a46SAndroid Build Coastguard Worker
234*6c119a46SAndroid Build Coastguard Worker            self._paths = tuple(paths)
235*6c119a46SAndroid Build Coastguard Worker
236*6c119a46SAndroid Build Coastguard Worker    @property
237*6c119a46SAndroid Build Coastguard Worker    def current_version(self) -> str:
238*6c119a46SAndroid Build Coastguard Worker        """Obtains the current version according to the metadata."""
239*6c119a46SAndroid Build Coastguard Worker        self._read_content()
240*6c119a46SAndroid Build Coastguard Worker
241*6c119a46SAndroid Build Coastguard Worker        match = re.search(r'version: "([^"]*)"', self._content)
242*6c119a46SAndroid Build Coastguard Worker        if not match:
243*6c119a46SAndroid Build Coastguard Worker            sys.exit(
244*6c119a46SAndroid Build Coastguard Worker                f'Error: Unable to determine current version from {self._metadata_path}'
245*6c119a46SAndroid Build Coastguard Worker            )
246*6c119a46SAndroid Build Coastguard Worker        return match.group(1)
247*6c119a46SAndroid Build Coastguard Worker
248*6c119a46SAndroid Build Coastguard Worker    @property
249*6c119a46SAndroid Build Coastguard Worker    def git_url(self) -> str:
250*6c119a46SAndroid Build Coastguard Worker        """Obtains the git URL to use from the metadata."""
251*6c119a46SAndroid Build Coastguard Worker        self._read_raw_git_urls()
252*6c119a46SAndroid Build Coastguard Worker        return self._url
253*6c119a46SAndroid Build Coastguard Worker
254*6c119a46SAndroid Build Coastguard Worker    @property
255*6c119a46SAndroid Build Coastguard Worker    def git_paths(self) -> list[pathlib.PurePath]:
256*6c119a46SAndroid Build Coastguard Worker        """Obtains the child paths to sync from the metadata.
257*6c119a46SAndroid Build Coastguard Worker
258*6c119a46SAndroid Build Coastguard Worker        This can be an empty list if the entire repo should be synced.
259*6c119a46SAndroid Build Coastguard Worker        """
260*6c119a46SAndroid Build Coastguard Worker        self._read_raw_git_urls()
261*6c119a46SAndroid Build Coastguard Worker        return list(self._paths)
262*6c119a46SAndroid Build Coastguard Worker
263*6c119a46SAndroid Build Coastguard Worker    def update_version_and_import_date(self, version: str) -> None:
264*6c119a46SAndroid Build Coastguard Worker        """Updates the version and import date in the metadata.
265*6c119a46SAndroid Build Coastguard Worker
266*6c119a46SAndroid Build Coastguard Worker        |version| gives the version string to write.
267*6c119a46SAndroid Build Coastguard Worker        The import date is set to the current date.
268*6c119a46SAndroid Build Coastguard Worker        """
269*6c119a46SAndroid Build Coastguard Worker        self._read_content()
270*6c119a46SAndroid Build Coastguard Worker
271*6c119a46SAndroid Build Coastguard Worker        now = datetime.datetime.now()
272*6c119a46SAndroid Build Coastguard Worker        self._content = re.sub(r'version: "[^"]*"', f'version: "{version}"',
273*6c119a46SAndroid Build Coastguard Worker                               self._content)
274*6c119a46SAndroid Build Coastguard Worker        self._content = re.sub(
275*6c119a46SAndroid Build Coastguard Worker            r'last_upgrade_date {[^}]*}',
276*6c119a46SAndroid Build Coastguard Worker            (f'last_upgrade_date {{ year: {now.year} month: {now.month} '
277*6c119a46SAndroid Build Coastguard Worker             f'day: {now.day} }}'), self._content)
278*6c119a46SAndroid Build Coastguard Worker
279*6c119a46SAndroid Build Coastguard Worker        self._write_content()
280*6c119a46SAndroid Build Coastguard Worker
281*6c119a46SAndroid Build Coastguard Worker
282*6c119a46SAndroid Build Coastguard Workerdef must_ignore(path: pathlib.PurePath) -> bool:
283*6c119a46SAndroid Build Coastguard Worker    """Checks if |path| should be ignored and not imported, as doing so might conflict with Android metadata.."""
284*6c119a46SAndroid Build Coastguard Worker    IGNORE_PATTERNS: tuple[str] = (
285*6c119a46SAndroid Build Coastguard Worker        'METADATA',
286*6c119a46SAndroid Build Coastguard Worker        'MODULE_LICENSE_*',
287*6c119a46SAndroid Build Coastguard Worker        '**/OWNERS',
288*6c119a46SAndroid Build Coastguard Worker        '**/Android.bp',
289*6c119a46SAndroid Build Coastguard Worker    )
290*6c119a46SAndroid Build Coastguard Worker    ignore = any(path.match(pattern) for pattern in IGNORE_PATTERNS)
291*6c119a46SAndroid Build Coastguard Worker    if ignore:
292*6c119a46SAndroid Build Coastguard Worker        print('Ignoring source {path}')
293*6c119a46SAndroid Build Coastguard Worker    return ignore
294*6c119a46SAndroid Build Coastguard Worker
295*6c119a46SAndroid Build Coastguard Worker
296*6c119a46SAndroid Build Coastguard Workerdef main():
297*6c119a46SAndroid Build Coastguard Worker    parser = argparse.ArgumentParser(
298*6c119a46SAndroid Build Coastguard Worker        description=DESCRIPTION,
299*6c119a46SAndroid Build Coastguard Worker        epilog=INTENDED_USAGE,
300*6c119a46SAndroid Build Coastguard Worker        formatter_class=argparse.RawDescriptionHelpFormatter)
301*6c119a46SAndroid Build Coastguard Worker
302*6c119a46SAndroid Build Coastguard Worker    parser.add_argument('group',
303*6c119a46SAndroid Build Coastguard Worker                        default=None,
304*6c119a46SAndroid Build Coastguard Worker                        help='The subdirectory (group) to update')
305*6c119a46SAndroid Build Coastguard Worker
306*6c119a46SAndroid Build Coastguard Worker    parser.add_argument(
307*6c119a46SAndroid Build Coastguard Worker        'version',
308*6c119a46SAndroid Build Coastguard Worker        nargs='?',
309*6c119a46SAndroid Build Coastguard Worker        default='HEAD',
310*6c119a46SAndroid Build Coastguard Worker        help='The official version to import. Uses HEAD by default.')
311*6c119a46SAndroid Build Coastguard Worker
312*6c119a46SAndroid Build Coastguard Worker    parser.add_argument('--loglevel',
313*6c119a46SAndroid Build Coastguard Worker                        default='INFO',
314*6c119a46SAndroid Build Coastguard Worker                        choices=('DEBUG', 'INFO', 'WARNING', 'ERROR',
315*6c119a46SAndroid Build Coastguard Worker                                 'CRITICAL'),
316*6c119a46SAndroid Build Coastguard Worker                        help='Logging level.')
317*6c119a46SAndroid Build Coastguard Worker
318*6c119a46SAndroid Build Coastguard Worker    parser.add_argument('--no-force-clean',
319*6c119a46SAndroid Build Coastguard Worker                        dest='force_clean',
320*6c119a46SAndroid Build Coastguard Worker                        default=True,
321*6c119a46SAndroid Build Coastguard Worker                        action='store_false',
322*6c119a46SAndroid Build Coastguard Worker                        help='Disables clean fetches of upstream code')
323*6c119a46SAndroid Build Coastguard Worker
324*6c119a46SAndroid Build Coastguard Worker    parser.add_argument(
325*6c119a46SAndroid Build Coastguard Worker        '--no-remove-old-files',
326*6c119a46SAndroid Build Coastguard Worker        dest='remove_old_files',
327*6c119a46SAndroid Build Coastguard Worker        default=True,
328*6c119a46SAndroid Build Coastguard Worker        action='store_false',
329*6c119a46SAndroid Build Coastguard Worker        help=
330*6c119a46SAndroid Build Coastguard Worker        'Disables syncing the previous version to determine what files to remove'
331*6c119a46SAndroid Build Coastguard Worker    )
332*6c119a46SAndroid Build Coastguard Worker
333*6c119a46SAndroid Build Coastguard Worker    args: argparse.ArgumentParser = parser.parse_args()
334*6c119a46SAndroid Build Coastguard Worker
335*6c119a46SAndroid Build Coastguard Worker    logging.basicConfig(level=getattr(logging, args.loglevel))
336*6c119a46SAndroid Build Coastguard Worker
337*6c119a46SAndroid Build Coastguard Worker    base = pathlib.Path(sys.argv[0]).parent.resolve().absolute()
338*6c119a46SAndroid Build Coastguard Worker    assert base.exists()
339*6c119a46SAndroid Build Coastguard Worker
340*6c119a46SAndroid Build Coastguard Worker    print(
341*6c119a46SAndroid Build Coastguard Worker        f'Importing {args.group} Wayland protocols at {args.version} to {args.group}'
342*6c119a46SAndroid Build Coastguard Worker    )
343*6c119a46SAndroid Build Coastguard Worker
344*6c119a46SAndroid Build Coastguard Worker    target_git = GitRepo(base)
345*6c119a46SAndroid Build Coastguard Worker    target_git.assert_no_uncommitted_changes()
346*6c119a46SAndroid Build Coastguard Worker    target_group_path = base / args.group
347*6c119a46SAndroid Build Coastguard Worker
348*6c119a46SAndroid Build Coastguard Worker    meta = AndroidMetadata(target_group_path / 'METADATA')
349*6c119a46SAndroid Build Coastguard Worker
350*6c119a46SAndroid Build Coastguard Worker    print(f'Cloning {meta.git_url} [sparse/limited] at {args.version}')
351*6c119a46SAndroid Build Coastguard Worker    import_new_git = GitRepo(base / '.import' / args.group / (args.version))
352*6c119a46SAndroid Build Coastguard Worker    import_new_git.sparse_depth1_clone(meta.git_url,
353*6c119a46SAndroid Build Coastguard Worker                                       args.version,
354*6c119a46SAndroid Build Coastguard Worker                                       meta.git_paths,
355*6c119a46SAndroid Build Coastguard Worker                                       force_clean=args.force_clean)
356*6c119a46SAndroid Build Coastguard Worker    import_new_hash = import_new_git.get_hash_for_version(args.version)
357*6c119a46SAndroid Build Coastguard Worker    import_new_ref_name = import_new_git.git_ref_name_for_version(args.version)
358*6c119a46SAndroid Build Coastguard Worker    print(f'Synced "{import_new_hash} ({import_new_ref_name})"')
359*6c119a46SAndroid Build Coastguard Worker    import_new_files = import_new_git.get_files(import_new_hash,
360*6c119a46SAndroid Build Coastguard Worker                                                meta.git_paths)
361*6c119a46SAndroid Build Coastguard Worker    if args.remove_old_files:
362*6c119a46SAndroid Build Coastguard Worker        print(
363*6c119a46SAndroid Build Coastguard Worker            f'Cloning {meta.git_url} [sparse/limited] at prior {meta.current_version}'
364*6c119a46SAndroid Build Coastguard Worker        )
365*6c119a46SAndroid Build Coastguard Worker        import_old_git = GitRepo(base / '.import' / args.group /
366*6c119a46SAndroid Build Coastguard Worker                                 meta.current_version)
367*6c119a46SAndroid Build Coastguard Worker        import_old_git.sparse_depth1_clone(meta.git_url,
368*6c119a46SAndroid Build Coastguard Worker                                           meta.current_version,
369*6c119a46SAndroid Build Coastguard Worker                                           meta.git_paths,
370*6c119a46SAndroid Build Coastguard Worker                                           force_clean=args.force_clean)
371*6c119a46SAndroid Build Coastguard Worker        import_old_hash = import_old_git.get_hash_for_version(
372*6c119a46SAndroid Build Coastguard Worker            meta.current_version)
373*6c119a46SAndroid Build Coastguard Worker        print(f'Synced "{import_old_hash}"')
374*6c119a46SAndroid Build Coastguard Worker        import_old_files = import_old_git.get_files(import_old_hash,
375*6c119a46SAndroid Build Coastguard Worker                                                    meta.git_paths)
376*6c119a46SAndroid Build Coastguard Worker
377*6c119a46SAndroid Build Coastguard Worker        files_to_remove = set(import_old_files).difference(import_new_files)
378*6c119a46SAndroid Build Coastguard Worker        for path in files_to_remove:
379*6c119a46SAndroid Build Coastguard Worker            if must_ignore(path):
380*6c119a46SAndroid Build Coastguard Worker                continue
381*6c119a46SAndroid Build Coastguard Worker            old: pathlib.Path = target_group_path / path
382*6c119a46SAndroid Build Coastguard Worker            logging.debug("removing old path %s", old)
383*6c119a46SAndroid Build Coastguard Worker            old.unlink(missing_ok=True)
384*6c119a46SAndroid Build Coastguard Worker
385*6c119a46SAndroid Build Coastguard Worker    for path in import_new_files:
386*6c119a46SAndroid Build Coastguard Worker        if must_ignore(path):
387*6c119a46SAndroid Build Coastguard Worker            continue
388*6c119a46SAndroid Build Coastguard Worker        src: pathlib.Path = import_new_git.base / path
389*6c119a46SAndroid Build Coastguard Worker        dst: pathlib.Path = target_group_path / path
390*6c119a46SAndroid Build Coastguard Worker        logging.debug("copying %s to %s", src, dst)
391*6c119a46SAndroid Build Coastguard Worker        dst.parent.mkdir(parents=True, exist_ok=True)
392*6c119a46SAndroid Build Coastguard Worker        shutil.copy(src, dst)
393*6c119a46SAndroid Build Coastguard Worker        target_git.add(target_group_path / path)
394*6c119a46SAndroid Build Coastguard Worker
395*6c119a46SAndroid Build Coastguard Worker    meta.update_version_and_import_date(import_new_ref_name or import_new_hash)
396*6c119a46SAndroid Build Coastguard Worker    target_git.add(target_group_path / 'METADATA')
397*6c119a46SAndroid Build Coastguard Worker
398*6c119a46SAndroid Build Coastguard Worker    message = f'''
399*6c119a46SAndroid Build Coastguard WorkerUpdate to {args.group} protocols {import_new_ref_name or import_new_hash}
400*6c119a46SAndroid Build Coastguard Worker
401*6c119a46SAndroid Build Coastguard WorkerThis imports {import_new_hash} from the upstream repository.
402*6c119a46SAndroid Build Coastguard Worker
403*6c119a46SAndroid Build Coastguard WorkerTest: Builds
404*6c119a46SAndroid Build Coastguard Worker'''.lstrip()
405*6c119a46SAndroid Build Coastguard Worker    target_git.commit(message, allow_empty=True)
406*6c119a46SAndroid Build Coastguard Worker
407*6c119a46SAndroid Build Coastguard Worker
408*6c119a46SAndroid Build Coastguard Workerif __name__ == '__main__':
409*6c119a46SAndroid Build Coastguard Worker    main()
410