"""Init file for Hass.io add-on Git.""" import asyncio import logging import functools as ft from pathlib import Path import shutil import git from .utils import get_hash_from_repository from ..const import URL_HASSIO_ADDONS, ATTR_URL, ATTR_BRANCH from ..coresys import CoreSysAttributes from ..validate import RE_REPOSITORY _LOGGER: logging.Logger = logging.getLogger(__name__) class GitRepo(CoreSysAttributes): """Manage Add-on Git repository.""" def __init__(self, coresys, path, url): """Initialize Git base wrapper.""" self.coresys = coresys self.repo = None self.path = path self.lock = asyncio.Lock(loop=coresys.loop) self.data = RE_REPOSITORY.match(url).groupdict() @property def url(self): """Return repository URL.""" return self.data[ATTR_URL] @property def branch(self): """Return repository branch.""" return self.data[ATTR_BRANCH] async def load(self): """Init Git add-on repository.""" if not self.path.is_dir(): return await self.clone() async with self.lock: try: _LOGGER.info("Load add-on %s repository", self.path) self.repo = await self.sys_run_in_executor(git.Repo, str(self.path)) except ( git.InvalidGitRepositoryError, git.NoSuchPathError, git.GitCommandError, ) as err: _LOGGER.error("Can't load %s repo: %s.", self.path, err) self._remove() return False return True async def clone(self): """Clone git add-on repository.""" async with self.lock: git_args = { attribute: value for attribute, value in ( ("recursive", True), ("branch", self.branch), ("depth", 1), ("shallow-submodules", True), ) if value is not None } try: _LOGGER.info("Clone add-on %s repository", self.url) self.repo = await self.sys_run_in_executor( ft.partial( git.Repo.clone_from, self.url, str(self.path), **git_args ) ) except ( git.InvalidGitRepositoryError, git.NoSuchPathError, git.GitCommandError, ) as err: _LOGGER.error("Can't clone %s repository: %s.", self.url, err) self._remove() return False return True async def pull(self): """Pull Git add-on repo.""" if self.lock.locked(): _LOGGER.warning("It is already a task in progress") return False async with self.lock: _LOGGER.info("Update add-on %s repository", self.url) branch = self.repo.active_branch.name try: # Download data await self.sys_run_in_executor( ft.partial( self.repo.remotes.origin.fetch, **{"update-shallow": True, "depth": 1}, ) ) # Jump on top of that await self.sys_run_in_executor( ft.partial(self.repo.git.reset, f"origin/{branch}", hard=True) ) # Cleanup old data await self.sys_run_in_executor(ft.partial(self.repo.git.clean, "-xdf")) except ( git.InvalidGitRepositoryError, git.NoSuchPathError, git.GitCommandError, ) as err: _LOGGER.error("Can't update %s repo: %s.", self.url, err) return False return True def _remove(self): """Remove a repository.""" if not self.path.is_dir(): return def log_err(funct, path, _): """Log error.""" _LOGGER.warning("Can't remove %s", path) shutil.rmtree(str(self.path), onerror=log_err) class GitRepoHassIO(GitRepo): """Hass.io add-ons repository.""" def __init__(self, coresys): """Initialize Git Hass.io add-on repository.""" super().__init__(coresys, coresys.config.path_addons_core, URL_HASSIO_ADDONS) class GitRepoCustom(GitRepo): """Custom add-ons repository.""" def __init__(self, coresys, url): """Initialize custom Git Hass.io addo-n repository.""" path = Path(coresys.config.path_addons_git, get_hash_from_repository(url)) super().__init__(coresys, path, url) def remove(self): """Remove a custom repository.""" _LOGGER.info("Remove custom add-on repository %s", self.url) self._remove()