diff options
| author | Simeon Simeonov | 2021-01-14 22:38:03 +0100 |
|---|---|---|
| committer | Simeon Simeonov | 2021-01-14 22:38:03 +0100 |
| commit | f2f3ee4a4d12741177ae691f56f502e2de61d63d (patch) | |
| tree | e34119d17db6ea2e96c851443d32387964d6aaa6 | |
| parent | 5aa1a9a1051e4eba031b877a0aeea8a4afb93e42 (diff) | |
Replace pygit2 with GitPython and use subprocess for all checks
| -rwxr-xr-x | pygitchecker.py | 183 |
1 files 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 @@ | |||
| 1 | #!/usr/bin/env python3 | 1 | #!/usr/bin/env python |
| 2 | 2 | # -*- coding: utf-8 -*- | |
| 3 | """Simple, on demand git checker""" | ||
| 3 | import argparse | 4 | import argparse |
| 4 | import os | 5 | import os |
| 6 | import subprocess | ||
| 5 | import sys | 7 | import sys |
| 6 | 8 | ||
| 7 | import pygit2 | 9 | import git |
| 10 | |||
| 8 | 11 | ||
| 9 | from flake8.api import legacy as flake8_legacy | 12 | class ValidationError(Exception): |
| 13 | """ValidationError""" | ||
| 10 | 14 | ||
| 11 | 15 | ||
| 12 | MODIFIED_STATUSES = (pygit2.GIT_STATUS_WT_MODIFIED, | 16 | def eprint(*arg, **kwargs): |
| 13 | pygit2.GIT_STATUS_INDEX_NEW, | 17 | """stdderr print wrapper""" |
| 14 | pygit2.GIT_STATUS_INDEX_MODIFIED) | 18 | print(*arg, file=sys.stderr, flush=True, **kwargs) |
| 15 | MODIFIED_STATUSES_FULL = (pygit2.GIT_STATUS_WT_MODIFIED, | ||
| 16 | pygit2.GIT_STATUS_WT_NEW, | ||
| 17 | pygit2.GIT_STATUS_INDEX_NEW, | ||
| 18 | pygit2.GIT_STATUS_INDEX_MODIFIED) | ||
| 19 | 19 | ||
| 20 | 20 | ||
| 21 | def is_git_repo(abspath): | 21 | def is_git_repo(abspath): |
| @@ -25,35 +25,25 @@ def is_git_repo(abspath): | |||
| 25 | if not os.path.isdir(abspath): | 25 | if not os.path.isdir(abspath): |
| 26 | return False | 26 | return False |
| 27 | try: | 27 | try: |
| 28 | pygit2.Repository(abspath) | 28 | git.Repo(abspath) |
| 29 | return True | 29 | return True |
| 30 | except pygit2.GitError: | 30 | except git.InvalidGitRepositoryError: |
| 31 | return False | 31 | return False |
| 32 | 32 | ||
| 33 | 33 | ||
| 34 | def modifications_exist(abspath, args): | 34 | def modifications_exist(abspath): |
| 35 | """ | 35 | """ |
| 36 | True if modifications exist for git repo representing abspath | 36 | True if modifications exist for git repo representing abspath |
| 37 | False if no modifications exist | 37 | False if no modifications exist |
| 38 | 38 | ||
| 39 | pygit2.GitError raised if abspath points to illegal repo | 39 | git.InvalidGitRepositoryError raised if abspath points to illegal repo |
| 40 | """ | 40 | """ |
| 41 | repo = pygit2.Repository(abspath) | 41 | repo = git.Repo(abspath) |
| 42 | status = repo.status() | 42 | return bool(repo.is_dirty() or repo.index.diff(None)) |
| 43 | if args.full_search: | ||
| 44 | statuses = MODIFIED_STATUSES_FULL | ||
| 45 | else: | ||
| 46 | statuses = MODIFIED_STATUSES | ||
| 47 | for gfile, state in status.items(): | ||
| 48 | if state in statuses: | ||
| 49 | return True | ||
| 50 | return False | ||
| 51 | 43 | ||
| 52 | 44 | ||
| 53 | def print_modification_status(abspath, status, args): | 45 | def print_modification_status(abspath, status, args): |
| 54 | """ | 46 | """Prints the modification status for the given abspath""" |
| 55 | Prints the modification status for the give abspath | ||
| 56 | """ | ||
| 57 | if status: | 47 | if status: |
| 58 | if args.verbose: | 48 | if args.verbose: |
| 59 | print(f'{abspath} -> modified') | 49 | print(f'{abspath} -> modified') |
| @@ -65,109 +55,141 @@ def print_modification_status(abspath, status, args): | |||
| 65 | return status | 55 | return status |
| 66 | 56 | ||
| 67 | 57 | ||
| 68 | def fetch_git_repos(abspath): | 58 | def fetch_git_repos(abspath: str): |
| 69 | """ | 59 | """Returns a list of git repo paths under abspath""" |
| 70 | Returns a list of git repo paths under abspath | 60 | repo_list = [] |
| 71 | """ | 61 | for root, dirs, _ in os.walk(abspath): |
| 72 | repo_list = list() | ||
| 73 | for root, dirs, files in os.walk(abspath): | ||
| 74 | if '/.git' in root: | 62 | if '/.git' in root: |
| 75 | continue | 63 | continue |
| 76 | if '.git' in dirs: | 64 | if '.git' in dirs: |
| 77 | try: | 65 | try: |
| 78 | pygit2.Repository(root) | 66 | git.Repo(root) |
| 79 | repo_list.append(root) | 67 | repo_list.append(root) |
| 80 | except pygit2.GitError: | 68 | except git.InvalidGitRepositoryError: |
| 81 | continue | 69 | continue |
| 82 | return repo_list | 70 | return repo_list |
| 83 | 71 | ||
| 84 | 72 | ||
| 85 | def fetch_git_files(args): | 73 | def fetch_git_files(args): |
| 86 | """ | 74 | """Fetches new and modified .py-files using git""" |
| 87 | Fetches new and modified .py-files using pygit2 | ||
| 88 | """ | ||
| 89 | git_files_list = list() | ||
| 90 | try: | 75 | try: |
| 91 | repo = pygit2.Repository(args.repo_path) | 76 | repo = git.Repo(args.repo_path) |
| 92 | status = repo.status() | 77 | return [mfile.a_path for mfile in repo.index.diff(None) |
| 93 | except pygit2.GitError: | 78 | if mfile.a_path.lower().endswith('.py')] |
| 94 | print('{repo_path} is not a valid git repository'.format( | 79 | except git.InvalidGitRepositoryError: |
| 95 | repo_path=args.repo_path), | 80 | eprint(f'{args.repo_path} is not a valid git repository') |
| 96 | file=sys.stderr, | ||
| 97 | flush=True) | ||
| 98 | sys.exit(1) | 81 | sys.exit(1) |
| 99 | if not status: | ||
| 100 | return git_files_list | ||
| 101 | for gfile, state in status.items(): | ||
| 102 | if state in MODIFIED_STATUSES and gfile.lower().endswith('.py'): | ||
| 103 | if args.verbose: | ||
| 104 | print(gfile) | ||
| 105 | git_files_list.append(gfile) | ||
| 106 | return git_files_list | ||
| 107 | 82 | ||
| 108 | 83 | ||
| 109 | def fetch_all_files(abspath): | 84 | def fetch_all_files(abspath): |
| 110 | """ | 85 | """Fetches all .py-files""" |
| 111 | Fetches all .py-files | 86 | all_files_list = [] |
| 112 | """ | 87 | for root, _, files in os.walk(abspath): |
| 113 | all_files_list = list() | ||
| 114 | for root, dirs, files in os.walk(abspath): | ||
| 115 | for f in files: | 88 | for f in files: |
| 116 | if os.path.splitext(f)[1].lower() == '.py': | 89 | if os.path.splitext(f)[1].lower() == '.py': |
| 117 | all_files_list.append(os.path.join(abspath, root, f)) | 90 | all_files_list.append(os.path.join(abspath, root, f)) |
| 118 | return all_files_list | 91 | return all_files_list |
| 119 | 92 | ||
| 120 | 93 | ||
| 121 | if __name__ == '__main__': | 94 | def validate_flake8(pfile): |
| 95 | """Validate using: flake8""" | ||
| 96 | try: | ||
| 97 | subprocess.run(['/usr/bin/flake8', pfile], check=True) | ||
| 98 | return True | ||
| 99 | except subprocess.CalledProcessError: | ||
| 100 | return False | ||
| 101 | |||
| 102 | |||
| 103 | def validate_isort(pfile): | ||
| 104 | """Validate using: isort --check --diff""" | ||
| 105 | try: | ||
| 106 | subprocess.run(['/usr/bin/isort', '--check', '--diff', pfile], | ||
| 107 | check=True) | ||
| 108 | return True | ||
| 109 | except subprocess.CalledProcessError: | ||
| 110 | return False | ||
| 111 | |||
| 112 | |||
| 113 | def validate_pylint(pfile): | ||
| 114 | """Validate using: pylint -r no --exit-zero""" | ||
| 115 | try: | ||
| 116 | subprocess.run(['/usr/bin/pylint', '-r', 'no', '--exit-zero', pfile], | ||
| 117 | check=True) | ||
| 118 | return True | ||
| 119 | except subprocess.CalledProcessError: | ||
| 120 | return False | ||
| 121 | |||
| 122 | |||
| 123 | def validate_python_file(pfile, args): | ||
| 124 | """ | ||
| 125 | Validates a single .py file | ||
| 126 | |||
| 127 | :return: False if validation fails, False otherwise | ||
| 128 | :rtype: bool | ||
| 129 | """ | ||
| 130 | if not validate_flake8(pfile) and not args.continue_checks: | ||
| 131 | if args.verbose: | ||
| 132 | eprint(f'{pfile}: flake8 validation failed') | ||
| 133 | sys.exit(0) | ||
| 134 | if not validate_isort(pfile) and not args.continue_checks: | ||
| 135 | if args.verbose: | ||
| 136 | eprint(f'{pfile}: isort validation failed') | ||
| 137 | sys.exit(0) | ||
| 138 | if not validate_pylint(pfile) and not args.continue_checks: | ||
| 139 | if args.verbose: | ||
| 140 | eprint(f'{pfile}: pylint validation failed') | ||
| 141 | sys.exit(0) | ||
| 142 | |||
| 143 | |||
| 144 | def main(inargs=None): | ||
| 145 | """Main entry point""" | ||
| 122 | parser = argparse.ArgumentParser( | 146 | parser = argparse.ArgumentParser( |
| 123 | description='The following options are available') | 147 | description='The following options are available') |
| 124 | parser.add_argument( | 148 | parser.add_argument( |
| 125 | 'repo_path', | 149 | 'repo_path', |
| 126 | metavar='REPO', | 150 | metavar='REPO', |
| 151 | nargs='?', | ||
| 127 | default=os.getcwd(), | 152 | default=os.getcwd(), |
| 128 | type=str, | ||
| 129 | help='Git repo path (default: cwd)') | 153 | help='Git repo path (default: cwd)') |
| 130 | parser.add_argument( | 154 | parser.add_argument( |
| 131 | '-a', '--all', | 155 | '-a', '--all-files', |
| 132 | action='store_true', | 156 | action='store_true', |
| 133 | dest='all_files', | 157 | dest='all_files', |
| 134 | default=False, | 158 | help='Check all files, not only the modified ones') |
| 135 | help='Check all .py files (not only the ones that are modified)') | 159 | parser.add_argument( |
| 160 | '-c', '--continue-checks', | ||
| 161 | action='store_true', | ||
| 162 | dest='continue_checks', | ||
| 163 | help='Continue with the checks if validation fails') | ||
| 136 | parser.add_argument( | 164 | parser.add_argument( |
| 137 | '-f', '--full', | 165 | '-f', '--full', |
| 138 | action='store_true', | 166 | action='store_true', |
| 139 | dest='full_search', | 167 | dest='full_search', |
| 140 | default=False, | ||
| 141 | help='Consider repos that contain untracked files (used with -m)') | 168 | help='Consider repos that contain untracked files (used with -m)') |
| 142 | parser.add_argument( | 169 | parser.add_argument( |
| 143 | '-m', '--modifications-only', | 170 | '-m', '--modifications-only', |
| 144 | action='store_true', | 171 | action='store_true', |
| 145 | dest='mod_only', | 172 | dest='mod_only', |
| 146 | default=False, | ||
| 147 | help='Only check for git modifications') | 173 | help='Only check for git modifications') |
| 148 | parser.add_argument( | 174 | parser.add_argument( |
| 149 | '-r', '--recursive', | 175 | '-r', '--recursive', |
| 150 | action='store_true', | 176 | action='store_true', |
| 151 | dest='recursive', | 177 | dest='recursive', |
| 152 | default=False, | ||
| 153 | help='Look for several repos recursively') | 178 | help='Look for several repos recursively') |
| 154 | parser.add_argument( | 179 | parser.add_argument( |
| 155 | '-v', '--verbose', | 180 | '-v', '--verbose', |
| 156 | action='store_true', | 181 | action='store_true', |
| 157 | dest='verbose', | 182 | dest='verbose', |
| 158 | default=False, | ||
| 159 | help='Verbosity') | 183 | help='Verbosity') |
| 160 | args = parser.parse_args() | 184 | args = parser.parse_args(inargs) |
| 161 | abs_repo_path = os.path.abspath(os.path.expanduser(args.repo_path)) | 185 | abs_repo_path = os.path.abspath(os.path.expanduser(args.repo_path)) |
| 162 | if args.mod_only: | 186 | if args.mod_only: |
| 163 | if not args.recursive: | 187 | if not args.recursive: |
| 164 | if not is_git_repo(abs_repo_path): | 188 | if not is_git_repo(abs_repo_path): |
| 165 | print(f'{abs_repo_path} is not a valid git repository', | 189 | eprint(f'{abs_repo_path} is not a valid git repository') |
| 166 | file=sys.stderr, | ||
| 167 | flush=True) | ||
| 168 | sys.exit(1) | 190 | sys.exit(1) |
| 169 | print_modification_status(abs_repo_path, | 191 | print_modification_status(abs_repo_path, |
| 170 | modifications_exist(abs_repo_path, args), | 192 | modifications_exist(abs_repo_path), |
| 171 | args) | 193 | args) |
| 172 | sys.exit(0) | 194 | sys.exit(0) |
| 173 | # resursive | 195 | # resursive |
| @@ -178,7 +200,7 @@ if __name__ == '__main__': | |||
| 178 | repo_list.sort() | 200 | repo_list.sort() |
| 179 | for repo_path in repo_list: | 201 | for repo_path in repo_list: |
| 180 | print_modification_status(repo_path, | 202 | print_modification_status(repo_path, |
| 181 | modifications_exist(repo_path, args), | 203 | modifications_exist(repo_path), |
| 182 | args) | 204 | args) |
| 183 | sys.exit(0) | 205 | sys.exit(0) |
| 184 | if args.all_files: | 206 | if args.all_files: |
| @@ -188,6 +210,11 @@ if __name__ == '__main__': | |||
| 188 | if not selected_files: | 210 | if not selected_files: |
| 189 | print('No files selected') | 211 | print('No files selected') |
| 190 | sys.exit(0) | 212 | sys.exit(0) |
| 191 | report = flake8_legacy.get_style_guide().check_files(selected_files) | 213 | for target_file in selected_files: |
| 192 | if report.total_errors: | 214 | if args.verbose: |
| 193 | print('Total errors: {errors}'.format(errors=report.total_errors)) | 215 | print(f'Validating {target_file}:') |
| 216 | validate_python_file(target_file, args) | ||
| 217 | |||
| 218 | |||
| 219 | if __name__ == '__main__': | ||
| 220 | main() | ||
