blob: 5b938a9e3479d0ac4aceee0cd9d1c6907700c157 [file] [log] [blame]
# Copyright 2020 The Pigweed Authors
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may not
# use this file except in compliance with the License. You may obtain a copy of
# the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations under
# the License.
"""Install and check status of Git repository-based packages."""
import os
import pathlib
import shutil
import subprocess
from typing import Union
import urllib.parse
import pw_package.package_manager
PathOrStr = Union[pathlib.Path, str]
def git_stdout(*args: PathOrStr,
show_stderr=False,
repo: PathOrStr = '.') -> str:
return subprocess.run(['git', '-C', repo, *args],
stdout=subprocess.PIPE,
stderr=None if show_stderr else subprocess.DEVNULL,
check=True).stdout.decode().strip()
def git(*args: PathOrStr,
repo: PathOrStr = '.') -> subprocess.CompletedProcess:
return subprocess.run(['git', '-C', repo, *args], check=True)
class GitRepo(pw_package.package_manager.Package):
"""Install and check status of Git repository-based packages."""
def __init__(self, url, *args, commit='', tag='', **kwargs):
super().__init__(*args, **kwargs)
if not (commit or tag):
raise ValueError('git repo must specify a commit or tag')
self._url = url
self._commit = commit
self._tag = tag
def status(self, path: pathlib.Path) -> bool:
if not os.path.isdir(path / '.git'):
return False
remote = git_stdout('remote', 'get-url', 'origin', repo=path)
url = urllib.parse.urlparse(remote)
if url.scheme == 'sso' or '.git.corp.google.com' in url.netloc:
host = url.netloc.replace(
'.git.corp.google.com',
'.googlesource.com',
)
if not host.endswith('.googlesource.com'):
host += '.googlesource.com'
remote = 'https://{}{}'.format(host, url.path)
commit = git_stdout('rev-parse', 'HEAD', repo=path)
if self._commit and self._commit != commit:
return False
if self._tag:
tag = git_stdout('describe', '--tags', repo=path)
if self._tag != tag:
return False
status = git_stdout('status', '--porcelain=v1', repo=path)
return remote == self._url and not status
def install(self, path: pathlib.Path) -> None:
# If already installed and at correct version exit now.
if self.status(path):
return
# Otherwise delete current version and clone again.
if os.path.isdir(path):
shutil.rmtree(path)
# --filter=blob:none means we don't get history, just the current
# revision. If we later run commands that need history it will be
# retrieved on-demand. For small repositories the effect is negligible
# but for large repositories this should be a significant improvement.
if self._commit:
git('clone', '--filter=blob:none', self._url, path)
git('reset', '--hard', self._commit, repo=path)
elif self._tag:
git('clone', '-b', self._tag, '--filter=blob:none', self._url,
path)