diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index ff85e8359a2edef24b667b0451522a3e0027188e..8079cb544b52c5e28866425f276319c46bb71c31 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -10,8 +10,11 @@ cache: lint: script: - - pip install pep8 + # Pylint 1.6.4 does not fully support PEP 484 type hints, which is why we also use pep8. + # When pylint 2.0 is released we could rely solely on pylint (without the --disable option) and remove pep8. + - pip install pep8 pylint - pep8 --max-line-length=120 vcsinfo/ tests/ + - pylint --max-line-length=120 --reports=no --disable=R,C vcsinfo test: script: diff --git a/README.md b/README.md index 445d703ff443d2a6b998572d7c8349562a7e524d..8d45026ad92e9894b85b0c8e9d304b1933d4bf39 100644 --- a/README.md +++ b/README.md @@ -10,7 +10,7 @@ Python abstraction layer for getting info from VCSes sych as git. ## Installation ``` -$ pip install git+https://gitlab.astro-wise.org/omegacen/vcsinfo.git +$ pip install git+https://gitlab.astro-wise.org/omegacen/vcsinfo.git@master ``` ## Usage @@ -19,9 +19,9 @@ You will first need instantiate a `Repository` instance. The preferred way to do this is via the factory method: ```python -from vcsinfo.factory import build +import vcsinfo.factory -repo = build('/path/to/my/git/repo') +repo = vcsinfo.factory.build('/path/to/my/git/repo') ``` The `repo` object can then be queried for info: diff --git a/tests/test_git.py b/tests/test_git.py index 2c963a458bd5a244e91d9878212eeb68d68952d7..25761a114125050d4d083ae3b2240841e0984bb1 100644 --- a/tests/test_git.py +++ b/tests/test_git.py @@ -1,7 +1,9 @@ -import pytest -import subprocess -import os import datetime +import os +import subprocess + +import pytest + from vcsinfo.vcsinfo import Repository from vcsinfo.factory import build from vcsinfo.exceptions import InvalidInputError, VcsError diff --git a/vcsinfo/implementations/git.py b/vcsinfo/implementations/git.py index 98745adb4e4024f2c4f74342095c403d8e05819a..06272ddaee2cfbb3625cba3bf69c5338eea8760c 100644 --- a/vcsinfo/implementations/git.py +++ b/vcsinfo/implementations/git.py @@ -1,16 +1,18 @@ """Git implementation of the VCS Info interfaces.""" -from ..vcsinfo import Repository, Version -from ..exceptions import InvalidInputError, VcsError -from datetime import datetime -from os.path import abspath -from pkg_resources import parse_version -from typing import Optional, Iterable +import datetime import fnmatch import functools +import os.path import re +import typing import subprocess +import pkg_resources + +from ..vcsinfo import Repository, Version +from ..exceptions import InvalidInputError, VcsError + class GitRepository(Repository): """Git implementation of a VCS repository.""" @@ -27,7 +29,7 @@ class GitRepository(Repository): raise InvalidInputError('{path} is not a git repository'.format(path=path)) super().__init__(path) - self.__path = abspath(path) + self.__path = os.path.abspath(path) @property def path(self) -> str: @@ -45,13 +47,13 @@ class GitRepository(Repository): """Check whether git is installed.""" try: output = subprocess.check_output(['git', '--version']).decode().strip() - git_version = re.search('([0-9]+\.)+[0-9]+', output).group(0) - return parse_version(cls.required_git_version) <= parse_version(git_version) + git_version = re.search(r'([0-9]+\.)+[0-9]+', output).group(0) + return pkg_resources.parse_version(cls.required_git_version) <= pkg_resources.parse_version(git_version) except (IndexError, FileNotFoundError, subprocess.CalledProcessError): return False @property - def branches(self) -> Iterable[str]: + def branches(self) -> typing.Iterable[str]: """Return all references (branches, tags) of this repository.""" self.fetch() return self.__run_git_command([ @@ -61,11 +63,12 @@ class GitRepository(Repository): ]).split('\n') @property - def remotes(self) -> Iterable[str]: + def remotes(self) -> typing.Iterable[str]: """Return all remotes of this repository.""" return self.__run_git_command(['remote']).split('\n') def fetch(self) -> None: + """Fetch updates from remote repositories.""" self.__run_git_command(['fetch', '--all']) def __run_git_command(self, params: list) -> str: @@ -96,9 +99,9 @@ class GitVersion(Version): raise InvalidInputError('{rev} is not a valid git revision'.format(rev=rev)) @property - def date(self) -> datetime: + def date(self) -> datetime.datetime: timestamp = self.__run_git_command(['show', '-s', '--format=%ct', self.id]) - return datetime.fromtimestamp(int(timestamp)) + return datetime.datetime.fromtimestamp(int(timestamp)) @property def repository(self) -> GitRepository: @@ -145,25 +148,25 @@ class GitVersion(Version): version += '.{symbol}'.format(symbol=replaced) return version - def newest(self, branch: Optional[str] = None) -> 'GitVersion': + def newest(self, branch: str = None) -> 'GitVersion': self.repository.fetch() if branch is not None: - pattern = '(({remotes})\/)?{branch}'.format( + pattern = r'(({remotes})\/)?{branch}'.format( remotes='|'.join(self.repository.remotes), branch=fnmatch.translate(branch) ) regex = re.compile(pattern) versions = [] - for b in self.repository.branches: - if branch is not None and not regex.match(b): + for rep_branch in self.repository.branches: + if branch is not None and not regex.match(rep_branch): continue - v = GitVersion(self.repository, b) - if not self.is_ancestor_of(v): + version = GitVersion(self.repository, rep_branch) + if not self.is_ancestor_of(version): continue - versions.append(v) + versions.append(version) if versions: return max(versions) @@ -180,7 +183,7 @@ class GitVersion(Version): except subprocess.CalledProcessError: return False - def last_change_on(self, filename: str) -> Optional['GitVersion']: + def last_change_on(self, filename: str) -> typing.Optional['GitVersion']: """Return the last version which changed the given file, looking back from this version. Returns: diff --git a/vcsinfo/vcsinfo.py b/vcsinfo/vcsinfo.py index 6eccfd83ebeba41205a9c4640c014733b245bf9a..a0e045c24c0a311b7d5936e1ea1bec9758862a98 100644 --- a/vcsinfo/vcsinfo.py +++ b/vcsinfo/vcsinfo.py @@ -1,14 +1,14 @@ """Interfaces for VCS repositories and versions""" -from abc import ABC, abstractmethod -from datetime import datetime -from typing import Optional +import abc +import datetime +import typing -class Repository(ABC): +class Repository(abc.ABC): """Interface for VCS repositories.""" - @abstractmethod + @abc.abstractmethod def __init__(self, path: str) -> None: """Repository constructor. @@ -18,18 +18,18 @@ class Repository(ABC): pass @property - @abstractmethod + @abc.abstractmethod def path(self) -> str: """Return the path of this repository.""" pass @classmethod - @abstractmethod + @abc.abstractmethod def is_compatible_path(cls, path: str) -> bool: """Check whether the given path is compatible with this type of VCS repository.""" pass - @abstractmethod + @abc.abstractmethod def current_version(self) -> 'Version': """Return the current version of the repository.""" pass @@ -39,37 +39,37 @@ class Repository(ABC): return self.path == other.path -class Version(ABC): +class Version(abc.ABC): """Interface for versions of a VCS repository A version always describes the repository as a whole, not just a single file. """ @property - @abstractmethod + @abc.abstractmethod def id(self) -> str: """The unique identifier of this version.""" pass @property - @abstractmethod + @abc.abstractmethod def version_id(self) -> str: """PEP 440 compatible version identifier.""" @property - @abstractmethod - def date(self) -> datetime: + @abc.abstractmethod + def date(self) -> datetime.datetime: """The date this version was created.""" pass @property - @abstractmethod + @abc.abstractmethod def repository(self) -> Repository: """The parent repository of this version.""" pass - @abstractmethod - def newest(self, branch: Optional[str] = None) -> 'Version': + @abc.abstractmethod + def newest(self, branch: str = None) -> 'Version': """The newest version in the parent repository which has this one as an ancestor. Args: @@ -78,8 +78,8 @@ class Version(ABC): """ pass - @abstractmethod - def last_change_on(self, filename: str) -> Optional['Version']: + @abc.abstractmethod + def last_change_on(self, filename: str) -> typing.Optional['Version']: """Return the last version which changed the given file, looking back from this version. Returns: