From f2f3ee4a4d12741177ae691f56f502e2de61d63d Mon Sep 17 00:00:00 2001 From: Simeon Simeonov Date: Thu, 14 Jan 2021 22:38:03 +0100 Subject: Replace pygit2 with GitPython and use subprocess for all checks --- pygitchecker.py | 183 ++++++++++++++++++++++++++++++++------------------------ 1 file changed, 105 insertions(+), 78 deletions(-) diff --git a/pygitchecker.py b/pygitchecker.py index 8dea428..4f4a872 100755 --- a/pygitchecker.py +++ b/pygitchecker.py @@ -1,21 +1,21 @@ -#!/usr/bin/env python3 - +#!/usr/bin/env python +# -*- coding: utf-8 -*- +"""Simple, on demand git checker""" import argparse import os +import subprocess import sys -import pygit2 +import git + -from flake8.api import legacy as flake8_legacy +class ValidationError(Exception): + """ValidationError""" -MODIFIED_STATUSES = (pygit2.GIT_STATUS_WT_MODIFIED, - pygit2.GIT_STATUS_INDEX_NEW, - pygit2.GIT_STATUS_INDEX_MODIFIED) -MODIFIED_STATUSES_FULL = (pygit2.GIT_STATUS_WT_MODIFIED, - pygit2.GIT_STATUS_WT_NEW, - pygit2.GIT_STATUS_INDEX_NEW, - pygit2.GIT_STATUS_INDEX_MODIFIED) +def eprint(*arg, **kwargs): + """stdderr print wrapper""" + print(*arg, file=sys.stderr, flush=True, **kwargs) def is_git_repo(abspath): @@ -25,35 +25,25 @@ def is_git_repo(abspath): if not os.path.isdir(abspath): return False try: - pygit2.Repository(abspath) + git.Repo(abspath) return True - except pygit2.GitError: + except git.InvalidGitRepositoryError: return False -def modifications_exist(abspath, args): +def modifications_exist(abspath): """ True if modifications exist for git repo representing abspath False if no modifications exist - pygit2.GitError raised if abspath points to illegal repo + git.InvalidGitRepositoryError raised if abspath points to illegal repo """ - repo = pygit2.Repository(abspath) - status = repo.status() - if args.full_search: - statuses = MODIFIED_STATUSES_FULL - else: - statuses = MODIFIED_STATUSES - for gfile, state in status.items(): - if state in statuses: - return True - return False + repo = git.Repo(abspath) + return bool(repo.is_dirty() or repo.index.diff(None)) def print_modification_status(abspath, status, args): - """ - Prints the modification status for the give abspath - """ + """Prints the modification status for the given abspath""" if status: if args.verbose: print(f'{abspath} -> modified') @@ -65,109 +55,141 @@ def print_modification_status(abspath, status, args): return status -def fetch_git_repos(abspath): - """ - Returns a list of git repo paths under abspath - """ - repo_list = list() - for root, dirs, files in os.walk(abspath): +def fetch_git_repos(abspath: str): + """Returns a list of git repo paths under abspath""" + repo_list = [] + for root, dirs, _ in os.walk(abspath): if '/.git' in root: continue if '.git' in dirs: try: - pygit2.Repository(root) + git.Repo(root) repo_list.append(root) - except pygit2.GitError: + except git.InvalidGitRepositoryError: continue return repo_list def fetch_git_files(args): - """ - Fetches new and modified .py-files using pygit2 - """ - git_files_list = list() + """Fetches new and modified .py-files using git""" try: - repo = pygit2.Repository(args.repo_path) - status = repo.status() - except pygit2.GitError: - print('{repo_path} is not a valid git repository'.format( - repo_path=args.repo_path), - file=sys.stderr, - flush=True) + repo = git.Repo(args.repo_path) + return [mfile.a_path for mfile in repo.index.diff(None) + if mfile.a_path.lower().endswith('.py')] + except git.InvalidGitRepositoryError: + eprint(f'{args.repo_path} is not a valid git repository') sys.exit(1) - if not status: - return git_files_list - for gfile, state in status.items(): - if state in MODIFIED_STATUSES and gfile.lower().endswith('.py'): - if args.verbose: - print(gfile) - git_files_list.append(gfile) - return git_files_list def fetch_all_files(abspath): - """ - Fetches all .py-files - """ - all_files_list = list() - for root, dirs, files in os.walk(abspath): + """Fetches all .py-files""" + all_files_list = [] + for root, _, files in os.walk(abspath): for f in files: if os.path.splitext(f)[1].lower() == '.py': all_files_list.append(os.path.join(abspath, root, f)) return all_files_list -if __name__ == '__main__': +def validate_flake8(pfile): + """Validate using: flake8""" + try: + subprocess.run(['/usr/bin/flake8', pfile], check=True) + return True + except subprocess.CalledProcessError: + return False + + +def validate_isort(pfile): + """Validate using: isort --check --diff""" + try: + subprocess.run(['/usr/bin/isort', '--check', '--diff', pfile], + check=True) + return True + except subprocess.CalledProcessError: + return False + + +def validate_pylint(pfile): + """Validate using: pylint -r no --exit-zero""" + try: + subprocess.run(['/usr/bin/pylint', '-r', 'no', '--exit-zero', pfile], + check=True) + return True + except subprocess.CalledProcessError: + return False + + +def validate_python_file(pfile, args): + """ + Validates a single .py file + + :return: False if validation fails, False otherwise + :rtype: bool + """ + if not validate_flake8(pfile) and not args.continue_checks: + if args.verbose: + eprint(f'{pfile}: flake8 validation failed') + sys.exit(0) + if not validate_isort(pfile) and not args.continue_checks: + if args.verbose: + eprint(f'{pfile}: isort validation failed') + sys.exit(0) + if not validate_pylint(pfile) and not args.continue_checks: + if args.verbose: + eprint(f'{pfile}: pylint validation failed') + sys.exit(0) + + +def main(inargs=None): + """Main entry point""" parser = argparse.ArgumentParser( description='The following options are available') parser.add_argument( 'repo_path', metavar='REPO', + nargs='?', default=os.getcwd(), - type=str, help='Git repo path (default: cwd)') parser.add_argument( - '-a', '--all', + '-a', '--all-files', action='store_true', dest='all_files', - default=False, - help='Check all .py files (not only the ones that are modified)') + help='Check all files, not only the modified ones') + parser.add_argument( + '-c', '--continue-checks', + action='store_true', + dest='continue_checks', + help='Continue with the checks if validation fails') parser.add_argument( '-f', '--full', action='store_true', dest='full_search', - default=False, help='Consider repos that contain untracked files (used with -m)') parser.add_argument( '-m', '--modifications-only', action='store_true', dest='mod_only', - default=False, help='Only check for git modifications') parser.add_argument( '-r', '--recursive', action='store_true', dest='recursive', - default=False, help='Look for several repos recursively') parser.add_argument( '-v', '--verbose', action='store_true', dest='verbose', - default=False, help='Verbosity') - args = parser.parse_args() + args = parser.parse_args(inargs) abs_repo_path = os.path.abspath(os.path.expanduser(args.repo_path)) if args.mod_only: if not args.recursive: if not is_git_repo(abs_repo_path): - print(f'{abs_repo_path} is not a valid git repository', - file=sys.stderr, - flush=True) + eprint(f'{abs_repo_path} is not a valid git repository') sys.exit(1) print_modification_status(abs_repo_path, - modifications_exist(abs_repo_path, args), + modifications_exist(abs_repo_path), args) sys.exit(0) # resursive @@ -178,7 +200,7 @@ if __name__ == '__main__': repo_list.sort() for repo_path in repo_list: print_modification_status(repo_path, - modifications_exist(repo_path, args), + modifications_exist(repo_path), args) sys.exit(0) if args.all_files: @@ -188,6 +210,11 @@ if __name__ == '__main__': if not selected_files: print('No files selected') sys.exit(0) - report = flake8_legacy.get_style_guide().check_files(selected_files) - if report.total_errors: - print('Total errors: {errors}'.format(errors=report.total_errors)) + for target_file in selected_files: + if args.verbose: + print(f'Validating {target_file}:') + validate_python_file(target_file, args) + + +if __name__ == '__main__': + main() -- cgit v1.3