вторник, 30 апреля 2013 г.

Перехватчики для Git на Python - это просто.

Не так давно решал интересную задачу: нужно было разработать простенький hook-скрипт для git. А если конкретнее, то отслеживать макс.длину измененных строк в файлах, добавленных в текущий коммит. Для этих целей служит специальный хук pre-commit (подробнее о них - тут). Разбираться с gitpy не было времени и желания. К тому же захотелось написать самому. Оказалось, это не так сложно. Практически все примеры хуков на Python, которые я нашёл в инете - все используют модуль subprocess для работы с git. И вообще, работа с git через Python (и не только) сводится к запуску команд git и парсингу результатов. Решением стал пакет simplegit, содержащий специальный класс Git():

...
class Git(object):
    """ class for a GIT interface
    """
    def _call_git(self, *params):
        """ call a git command with params
        """
        p = subprocess.Popen(
            ["git"] + list(params),
            stdin=subprocess.PIPE,
            stdout=subprocess.PIPE,
            stderr=subprocess.PIPE,
            shell=False
            )
        exit_code = p.wait()
        if exit_code != 0:
            raise GitException("Error: git exit code = %s" % exit_code)

        return p.stdout.read().strip()

    def config(self, *options):
        """ run: git config 
        """
        return self._call_git("config", *options)

    def get_param(self, param, default=None):
        """ get one param from git config
        """
        try:
            res = self.config("--get", param)
            return res
        except GitException:
            return default

    def set_param(self, param, value, filename=""):
        """ set one param
        """
        if filename:
            self.config("-f", filename, param, value)
        else:
            self.config(param, value)

    def del_param(self, param):
        """ remove parameter
        """
        self.config("--unset", param)

    def check_git(self):
        """ check: git is installed?
        """
        try:
            self._call_git("--version")
            return True
        except OSError:
            return False

    def get_files(self):
        """ return a file list... or empty list
        """
        ret = self._call_git(
             'diff',
             '--cached',
             '--diff-filter=AM',
             '-U0',
             '--name-only')
        if ret:
            return ret.split('\n')
        else:
            return []
...
Чтобы написать скрипт, реализующий перехватчик (hook) - необходимо создать свой класс, унаследовав его от simplegit.Git и дописав необходимый функционал:

class GitHook(Git):
    """ simple git hook for a client side
    """
    def check_max_length(self, filename, default_line_length=80):
        """ check max length of source lines
        """
        # get a config
        max_line_length = self.get_param("pre-commit.max-line-length",
                                         default_line_length)
        
        try:
            max_line_length = int(max_line_length)
        except:
            max_line_length = default_line_length

        # get a lines and check
        for n,s in self.get_diff_rows(filename):
            length = len(s)
            if length > max_line_length:
                print "%s:%d:Line has length %d but %d is allowed" % 
                    (filename, n, length, max_line_length)
                print "%s[...]" % s[:80]
                return False

        return True

    def check_file(self, filename):
        if self.get_param("pre-commit.max-line-length.enabled") != "false":
            res = self.check_max_length(filename)
            if not res:
                return False
            
        # ... another check-routine place here...
        return True

    def check(self):
        """ check all files in a commit
        """
        for filename in self.get_files():
            print filename
            if not self.check_file(filename):
                return False

        # ... otherwise check routines place here...
        return True


if __name__ == "__main__":
    print "[PRE-COMMIT HOOK] Check a files...."
    hook = GitHook()
    if hook.check():
        exit(0)
    else:
        exit(1)

Примечание: проект тестировался на Ubuntu 12.04 и FreeBSD 9.1, на версии Python 2.7.3
Исходники проекта с инсталляторами и юнит-тестами на github: тут

2 комментария:

  1. Спасибо за статью, я немного изменил, чтобы работало в Windows - http://plutov.by/post/git_pre_commit_windows

    ОтветитьУдалить
  2. Casinos in India | Wooricasinos
    What is a Casinos in 킹스 포커 India? Indian Casinos 사카미치 마루 - List of all Indian Online 강원 랜드 칩걸 Casinos ✓ Check the best 텐벳 먹튀 Indian Casino Sites ✓ Try for free 아 샤벳 now!

    ОтветитьУдалить