import json import os import subprocess import urllib.parse from pathlib import Path from .. import conf as osc_conf from .. import oscerr class GitStore: @classmethod def is_project_dir(cls, path): try: store = cls(path) except oscerr.NoWorkingCopy: return False return store.is_project @classmethod def is_package_dir(cls, path): try: store = cls(path) except oscerr.NoWorkingCopy: return False return store.is_package def __init__(self, path, check=True): self.path = path self.abspath = os.path.abspath(self.path) try: self.toplevel = self._run_git(["rev-parse", "--show-toplevel"]) self.toplevel = os.path.abspath(self.toplevel) except subprocess.CalledProcessError: self.toplevel = None # TODO: how to determine if the current git repo contains a project or a package? self.is_project = False self.is_package = False if self.toplevel: # NOTE: we have only one store in project-git for all packages config_path = os.path.join(self.toplevel, "_config") pbuild_path = os.path.join(self.toplevel, "_pbuild") if self.toplevel == self.abspath and (os.path.isfile(config_path) or os.path.isfile(pbuild_path)): self.is_project = True self.is_package = False else: self.is_project = False self.is_package = True self._package = None self._project = None if check and not any([self.is_project, self.is_package]): msg = f"Directory '{self.path}' is not a Git SCM working copy" raise oscerr.NoWorkingCopy(msg) if check and not self.scmurl: msg = f"Directory '{self.path}' is a Git SCM repo that lacks the 'origin' remote" raise oscerr.NoWorkingCopy(msg) # TODO: decide if we need explicit 'git lfs pull' or not # self._run_git(["lfs", "pull"]) def assert_is_project(self): if not self.is_project: msg = f"Directory '{self.path}' is not a Git SCM working copy of a project" raise oscerr.NoWorkingCopy(msg) def assert_is_package(self): if not self.is_package: msg = f"Directory '{self.path}' is not a Git SCM working copy of a package" raise oscerr.NoWorkingCopy(msg) def _run_git(self, args): return subprocess.check_output(["git"] + args, encoding="utf-8", cwd=self.abspath).strip() @property def apiurl(self): from ..obs_scm.store import Store # read apiurl from the current directory with .osc metadata # NOTE: this never triggers if a store is retrieved from osc.store.get_store(), # because obs_scm store takes precedence as .osc is present store = Store(self.toplevel, check=False) if store.apiurl: return store.apiurl # read project from parent directory that contains a project with .osc metadata store = Store(os.path.join(self.toplevel, ".."), check=False) if store.is_project and store.apiurl: return store.apiurl # HACK: we're using the currently configured apiurl return osc_conf.config["apiurl"] @property def project(self): from ..obs_scm.store import Store # read project from the current directory with .osc metadata # NOTE: this never triggers if a store is retrieved from osc.store.get_store(), # because obs_scm store takes precedence as .osc is present if self._project is None: store = Store(self.toplevel, check=False) if store.project: self._project = store.project # read project from parent directory that contains a project with .osc metadata if self._project is None: store = Store(os.path.join(self.toplevel, ".."), check=False) if store.is_project and store.project: self._project = store.project # guess project from git branch if self._project is None: # get project from the branch name branch = self._run_git(["branch", "--show-current"]) # HACK: replace hard-coded mapping with metadata from git or the build service # NOTE: you never know which git repo is supposed to be used in which project if branch == "factory": self._project = "openSUSE:Factory" else: raise oscerr.NoWorkingCopy(f"Couldn't map git branch '{branch}' to a project") return self._project @project.setter def project(self, value): self._project = value @property def package(self): if self._package is None: origin = self._run_git(["remote", "get-url", "origin"]) self._package = Path(urllib.parse.urlsplit(origin).path).stem return self._package @package.setter def package(self, value): self._package = value def _get_option(self, name): try: result = self._run_git(["config", "--local", "--get", f"osc.{name}"]) except subprocess.CalledProcessError: result = None return result def _check_type(self, name, value, expected_type): if not isinstance(value, expected_type): raise TypeError(f"The option '{name}' should be {expected_type.__name__}, not {type(value).__name__}") def _set_option(self, name, value): self._run_git(["config", "--local", f"osc.{name}", value]) def _unset_option(self, name): try: self._run_git(["config", "--local", "--unset", f"osc.{name}"]) except subprocess.CalledProcessError: pass def _get_dict_option(self, name): result = self._get_option(name) if result is None: return None result = json.loads(result) self._check_type(name, result, dict) return result def _set_dict_option(self, name, value): if value is None: self._unset_option(name) return self._check_type(name, value, dict) value = json.dumps(value) self._set_option(name, value) @property def last_buildroot(self): self.assert_is_package() result = self._get_dict_option("last-buildroot") if result is not None: result = (result["repo"], result["arch"], result["vm_type"]) return result @last_buildroot.setter def last_buildroot(self, value): self.assert_is_package() if len(value) != 3: raise ValueError("A tuple with exactly 3 items is expected: (repo, arch, vm_type)") value = { "repo": value[0], "arch": value[1], "vm_type": value[2], } self._set_dict_option("last-buildroot", value) @property def scmurl(self): try: return self._run_git(["remote", "get-url", "origin"]) except subprocess.CalledProcessError: return None