Source code for evariste.plugins.vcs.git

# Copyright Louis Paternault 2015-2022
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program.  If not, see <http://www.gnu.org/licenses/>.

"""Access to git-versionned files.

.. autoclass:: Git
   :members:
   :special-members: __contains__
"""

import datetime
import logging
import os
import pathlib
import typing
from datetime import datetime

import git

from . import VCS, NoRepositoryError

LOGGER = logging.getLogger(__name__)


[docs] class Git(VCS): """Access git-versionned files""" # pylint: disable=no-member keyword = "vcs.git" def __init__(self, shared): super().__init__(shared) try: self.repository = git.Repo( self.source.as_posix(), search_parent_directories=True ) except git.InvalidGitRepositoryError as error: raise NoRepositoryError(self.keyword, self.source) from error self._last_modified = self._read_modification_date() def _read_modification_date(self): """Return a dictionary of versionned files and their last modification time.""" # Thanks to Marian https://stackoverflow.com/a/35464230 LOGGER.info("Reading git commit dates…") last_modified = {} for blob in self.repository.tree(): if not os.path.isfile(blob.path): continue commit = next(self.repository.iter_commits(paths=blob.path, max_count=1)) last_modified[pathlib.Path(blob.path)] = datetime.fromtimestamp( commit.committed_date ) # Files not committed yet for blob in self.repository.index.iter_blobs(): if blob[1].path not in last_modified: last_modified[pathlib.Path(blob[1].path)] = super().last_modified( self.workdir / blob[1].path ) LOGGER.info("Done") return last_modified
[docs] def walk(self) -> typing.Iterable[pathlib.Path]: for entry in self._last_modified: try: yield (self.workdir / entry).relative_to(self.source) except ValueError: # `self.source` is not a subpath of `path` continue
[docs] def __contains__(self, path: pathlib.Path) -> bool: try: return self.from_repo(path) in self._last_modified except ValueError: # ``path`` is not a subpath of ``self.workdir`` return False
@property def workdir(self) -> pathlib.Path: return pathlib.Path(self.repository.working_dir)
[docs] def last_modified(self, path: pathlib.Path) -> datetime: if path not in self: return super().last_modified(path) return self._last_modified[self.from_repo(path)]