Source code for nodeps.extras.repo

"""NoDeps Extras Repo Module."""
from __future__ import annotations

__all__ = (
    "Repo",
)

import dataclasses
import os
import pathlib
import urllib
import urllib.parse
from typing import IO, AnyStr, TypeAlias

try:
    # nodeps[repo] extras
    from git import Git as GitCmd  # type: ignore[attr-defined]
    from git import GitCmdObjectDB, GitConfigParser  # type: ignore[attr-defined]
    from git import Repo as GitRepo  # type: ignore[attr-defined]
    from gitdb import LooseObjectDB  # type: ignore[attr-defined]
except ModuleNotFoundError:
    GitCmd = None
    GitCmdObjectDB = None
    GitConfigParser = None
    GitRepo = object
    LooseObjectDB = None

AnyPath: TypeAlias = os.PathLike | AnyStr | IO[AnyStr]


[docs] @dataclasses.dataclass class Repo(GitRepo): """Dataclass Wrapper for :class:`git.Repo`. Represents a git repository and allows you to query references, gather commit information, generate diffs, create and clone repositories query the log. 'working_tree_dir' is the working tree directory, but will raise AssertionError if we are a bare repository. Examples: >>> from nodeps.extras.repo import Repo >>> >>> repo = Repo() # doctest: +SKIP >>> repo.config_writer().set_value("user", "name", "root").release() # doctest: +SKIP """ git: GitCmd = dataclasses.field(init=False) """ The Repo class manages communication with the Git binary. It provides a convenient interface to calling the Git binary, such as in:: g = Repo( git_dir ) g.init() # calls 'git init' program rval = g.ls_files() # calls 'git ls-files' program ``Debugging`` Set the GIT_PYTHON_TRACE environment variable print each invocation of the command to stdout. Set its value to 'full' to see details about the returned values. """ git_dir: AnyPath | None = dataclasses.field(default=None, init=False) """the .git repository directory, which is always set""" odb: type[LooseObjectDB] = dataclasses.field(init=False) working_dir: AnyPath | None = dataclasses.field(default=None, init=False) """working directory of the git command, which is the working tree directory if available or the .git directory in case of bare repositories""" path: dataclasses.InitVar[AnyPath | None] = None """File or Directory inside the git repository, the default with search_parent_directories""" expand_vars: dataclasses.InitVar[bool] = True odbt: dataclasses.InitVar[type[LooseObjectDB]] = GitCmdObjectDB """the path to either the root git directory or the bare git repo""" search_parent_directories: dataclasses.InitVar[bool] = True """if True, all parent directories will be searched for a valid repo as well.""" def __post_init__(self, path, expand_vars, odbt, search_parent_directories): """Create a new Repo instance. Examples: >>> import warnings >>> import nodeps >>> from nodeps import Repo, Path >>> if not Path(nodeps.__file__).installed(): ... assert Repo(nodeps.__file__) >>> Repo("~/repo.git") # doctest: +ELLIPSIS +IGNORE_EXCEPTION_DETAIL Traceback (most recent call last): git.exc.NoSuchPathError: .../repo.git >>> warnings.simplefilter("ignore", UserWarning) >>> Repo("${HOME}/repo") # doctest: +ELLIPSIS +IGNORE_EXCEPTION_DETAIL Traceback (most recent call last): git.exc.NoSuchPathError: .../repo Raises: InvalidGitRepositoryError NoSuchPathError Args: path: File or Directory inside the git repository, the default with search_parent_directories set to True or the path to either the root git directory or the bare git repo if search_parent_directories is changed to False expand_vars: if True, environment variables will be expanded in the given path odbt: odbt search_parent_directories: Search all parent directories for a git repository. Returns: Repo: Repo instance """ if GitRepo == object: msg = "GitPython is not installed: installed with 'pip install nodeps[repo]'" raise ImportError(msg) if path: path = p.parent if (p := pathlib.Path(path)).is_file() else p super().__init__( path or pathlib.Path.cwd(), expand_vars=expand_vars, odbt=odbt, search_parent_directories=search_parent_directories, ) @property def git_config(self): """Wrapper for :func:`git.Repo.config_reader`, so it is already read and can be used. The configuration will include values from the system, user and repository configuration files. Examples: >>> import nodeps >>> from nodeps import Repo, Path >>> >>> if not Path(nodeps.__file__).installed(): ... conf = Repo(__file__).git_config ... assert conf.has_section('remote "origin"') is True ... assert conf.has_option('remote "origin"', 'url') is True ... assert 'https://github.com/' in conf.get('remote "origin"', 'url') ... assert 'https://github.com/' in conf.get_value('remote "origin"', 'url', "") Returns: GitConfigParser: GitConfigParser instance """ config = self.config_reader() config.read() return config @property def origin_url(self): """Git Origin URL. Examples: >>> import nodeps >>> from nodeps import Repo, Path >>> >>> if not Path(nodeps.__file__).installed(): ... assert 'https://github.com' in Repo(nodeps.__file__).origin_url.geturl() """ return urllib.parse.urlparse(self.git_config.get_value('remote "origin"', "url", "")) @property def top(self): """Repo Top Directory Path.""" return pathlib.Path(self.working_dir)