123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165 |
- # -*- coding: utf-8 -*-
- """
- Wrap python git interface for compatibility with older/newer version
- """
- try:
- from itertools import zip_longest
- except ImportError:
- from six.moves import zip_longest
- import logging
- import os
- from time import mktime
- from datetime import datetime
- from pelican.utils import set_date_tzinfo
- from git import Git, Repo
- DEV_LOGGER = logging.getLogger(__name__)
- def grouper(iterable, n, fillvalue=None):
- '''
- Collect data into fixed-length chunks or blocks
- '''
- # grouper('ABCDEFG', 3, 'x') --> ABC DEF Gxx
- args = [iter(iterable)] * n
- return zip_longest(fillvalue=fillvalue, *args)
- class _GitWrapperCommon(object):
- '''
- Wrap git module to provide a more stable interface across versions
- '''
- def __init__(self, repo_path):
- self.git = Git()
- self.repo = Repo(os.path.abspath('.'))
- def is_file_managed_by_git(self, path):
- '''
- :param path: Path to check
- :returns: True if path is managed by git
- '''
- status, _stdout, _stderr = self.git.execute(
- ['git', 'ls-files', path, '--error-unmatch'],
- with_extended_output=True,
- with_exceptions=False)
- return status == 0
- def is_file_modified(self, path):
- '''
- Does a file have local changes not yet committed
- :returns: True if file has local changes
- '''
- status, _stdout, _stderr = self.git.execute(
- ['git', 'diff', '--quiet', 'HEAD', path],
- with_extended_output=True,
- with_exceptions=False)
- return status != 0
- def get_commits_following(self, path):
- '''
- Get all commits including path following the file through
- renames
- :param path: Path which we will find commits for
- :returns: Sequence of commit objects. Newest to oldest
- '''
- return [
- commit for commit, _ in self.get_commits_and_names_iter(
- path)]
- def get_commits_and_names_iter(self, path):
- '''
- Get all commits including a given path following renames
- '''
- log_result = self.git.log(
- '--pretty=%H',
- '--follow',
- '--name-only',
- '--',
- path).splitlines()
- for commit_sha, _, filename in grouper(log_result, 3):
- yield self.repo.commit(commit_sha), filename
- def get_commits(self, path, follow=False):
- '''
- Get all commits including path
- :param path: Path which we will find commits for
- :param bool follow: If True we will follow path through renames
- :returns: Sequence of commit objects. Newest to oldest
- '''
- if follow:
- return self.get_commits_following(path)
- else:
- return self._get_commits(path)
- class _GitWrapperLegacy(_GitWrapperCommon):
- def _get_commits(self, path):
- '''
- Get all commits including path without following renames
- :param path: Path which we will find commits for
- :returns: Sequence of commit objects. Newest to oldest
- '''
- return self.repo.commits(path=path)
- @staticmethod
- def get_commit_date(commit, tz_name):
- '''
- Get datetime of commit comitted_date
- '''
- return set_date_tzinfo(
- datetime.fromtimestamp(mktime(commit.committed_date)),
- tz_name=tz_name)
- class _GitWrapper(_GitWrapperCommon):
- def _get_commits(self, path):
- '''
- Get all commits including path without following renames
- :param path: Path which we will find commits for
- :returns: Sequence of commit objects. Newest to oldest
- .. NOTE ::
- If this fails it could be that your gitpython version is out of
- sync with the git binary on your distro.
- Make sure you use the correct gitpython version.
- Alternatively enabling GIT_FILETIME_FOLLOW may also make your
- problem go away.
- '''
- return list(self.repo.iter_commits(paths=path))
- @staticmethod
- def get_commit_date(commit, tz_name):
- '''
- Get datetime of commit comitted_date
- '''
- return set_date_tzinfo(
- datetime.fromtimestamp(commit.committed_date),
- tz_name=tz_name)
- _wrapper_cache = {}
- def git_wrapper(path):
- '''
- Get appropriate wrapper factory and cache instance for path
- '''
- path = os.path.abspath(path)
- if path not in _wrapper_cache:
- if hasattr(Repo, 'commits'):
- _wrapper_cache[path] = _GitWrapperLegacy(path)
- else:
- _wrapper_cache[path] = _GitWrapper(path)
- return _wrapper_cache[path]
|