Commit a2b1c2f3 authored by Teake Nutma's avatar Teake Nutma
Browse files

Merge branch 'develop' into 'master'

Develop

See merge request !9
parents a015054e 1215e657
......@@ -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:
......
......@@ -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:
......
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
......
"""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:
......
"""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:
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment