PK,_M[Zhookmeup/__init__.py# -*- coding: utf-8 -*- """A Git hook to automate your Pipenv and Django workflows""" from __future__ import print_function import argparse from argparse import Namespace from . import hookmeup from .hookmeup import HookMeUpError __author__ = 'Daniel Moch' __version__ = '1.0.2' def main(): """Main hookmeup entrypoint""" parser = argparse.ArgumentParser() parser.add_argument( '-v', action='version', version='%(prog)s {}'.format(__version__) ) subparsers = parser.add_subparsers( title='subcommands', description='Valid %(prog)s subcommands. See more \ information on a subcommand by typing %(prog)s \ {subcommand} {-h,--help}' ) install_parser = subparsers.add_parser( 'install', description='Run inside a repository to install the hook. \ Fails if the current directory is not inside a Git \ repository.' ) install_parser.set_defaults(func=hookmeup.install) remove_parser = subparsers.add_parser( 'remove', description="Run inside a repository to uninstall the hook. \ Fails if the current directory is not inside a Git \ repository." ) remove_parser.set_defaults(func=hookmeup.remove) post_commit_parser = subparsers.add_parser( 'post-checkout', description='Run post-checkout hook. This should normally \ be called by Git automatically.') post_commit_parser.add_argument('old', help='the old commit') post_commit_parser.add_argument('new', help='the new commit') post_commit_parser.add_argument( 'branch_checkout', type=int, help='1 for branch checkout, 0 otherwise') post_commit_parser.set_defaults(func=hookmeup.post_checkout) args = parser.parse_args() if args == Namespace(): parser.print_help() exit(1) func = args.func arg_dict = vars(args) del arg_dict['func'] try: func(arg_dict) except HookMeUpError as ex: print(str(ex)) exit(ex.EXIT_CODE) PK 0M0sshookmeup/__main__.py# -*- coding: utf-8 -*- """hookmeup main module""" import hookmeup if __name__ == '__main__': hookmeup.main() PK0MМ]]hookmeup/hookmeup.py# -*- coding: utf-8 -*- """hookmeup module.""" from __future__ import print_function import os import subprocess from subprocess import CalledProcessError FORMAT_STRING = 'hookmeup: {}' def _print_msg(msg): """Print a formatted message to stdout""" print(FORMAT_STRING.format(msg)) class HookMeUpError(Exception): """Errors raised by hookmeup""" EXIT_CODE = 1 def __str__(self): return FORMAT_STRING.format(self.args[0]) class _DjangoMigrator(): """ Class responsible for parsing, applying, and unapplying Django migrations """ def __init__(self, args): self.added_migration_apps = [] self.oldest_deleted = {} self._migrate_command = ['pipenv', 'run', 'python', 'manage.py', 'migrate'] deleted_migrations = {} stdout = _call_checked_subprocess( ['git', 'diff', '--name-status', args['old'], args['new']], 'not in a Git repository' ) diff_lines = stdout.splitlines() for line in diff_lines: if line.find(os.path.sep + 'migrations' + os.path.sep) >= 0: file_status = line[0] file_path = line[1:-1].strip() file_path_segments = file_path.split(os.path.sep) migration_name = file_path_segments[-1].replace('.py', '') app_name = file_path_segments[-3] if file_status in ['D', 'M']: if app_name not in deleted_migrations: deleted_migrations[app_name] = [] deleted_migrations[app_name].append(migration_name) if file_status == 'A' \ and app_name not in self.added_migration_apps: self.added_migration_apps.append(app_name) for app_name, migrations_list in deleted_migrations.items(): migrations_list.sort() self.oldest_deleted[app_name] = \ int(migrations_list[0].split('_')[0]) def migrations_changed(self): """ Returns true if there are migrations that need to be applied or unapplied """ return self.added_migration_apps != [] or \ self.oldest_deleted != {} def migrate(self): """Apply/unapply any migrations as necessary""" for app, oldest in self.oldest_deleted.items(): target_migration = format(oldest - 1, '04d') if target_migration == '0000': target_migration = 'zero' _call_checked_subprocess( self._migrate_command + [app, target_migration], 'rollback migration for {} failed'.format(app) ) if self.added_migration_apps != []: _call_checked_subprocess( self._migrate_command + self.added_migration_apps, 'migration failed' ) def _call_checked_subprocess(arg_list, msg="fatal error"): """Handle return data from a call to a subprocess""" try: return subprocess.check_output(arg_list, text=True) except CalledProcessError: raise HookMeUpError(msg) def _adjust_pipenv(): """Adjust pipenv to match Pipfile""" _print_msg('Adjusting virtualenv to match Pipfile') _call_checked_subprocess( ['pipenv', 'clean'], 'Attempt to clean pipenv failed' ) _call_checked_subprocess( ['pipenv', 'sync', '--dev'], 'Attempt to sync pipenv failed' ) def _pipfile_changed(args): """Test if the Pipfile has changed""" stdout = _call_checked_subprocess( ['git', 'diff', '--name-only', args['old'], args['new'], '--', 'Pipfile', 'Pipfile.lock'], 'Not in a Git repository' ) return 'Pipfile' in str(stdout) def post_checkout(args): """Run post-checkout hook""" if args['branch_checkout'] == 1: migrator = _DjangoMigrator(args) if migrator.migrations_changed(): migrator.migrate() if _pipfile_changed(args): _adjust_pipenv() def install(args): """Install hook into repository""" if len(args) is not 0: raise HookMeUpError( "Argument passed to 'install', but expected none" ) stdout = _call_checked_subprocess( ['git', 'rev-parse', '--git-dir'], 'Not in a Git repository' ) hook_path = os.path.join( stdout.strip(), 'hooks', 'post-checkout' ) if os.path.exists(hook_path): with open(hook_path, 'r') as hook_file: already_installed = 'hookmeup' in hook_file.read() if already_installed: _print_msg('already installed') else: _print_msg('installing to existing hook') with open(hook_path, 'a') as hook_file: hook_file.write('hookmeup post-checkout "$@"\n') else: _print_msg('creating hook') with open(hook_path, 'w') as hook_file: hook_file.write('#!/bin/sh\nhookmeup post-checkout "$@"\n') def remove(args): """Remove the hook from the repository""" if len(args) is not 0: raise HookMeUpError( "Argument passed to 'remove', but expected none" ) stdout = _call_checked_subprocess( ['git', 'rev-parse', '--git-dir'], 'Not in a Git repository' ) hook_path = os.path.join( stdout.strip(), 'hooks', 'post-checkout' ) if os.path.exists(hook_path): with open(hook_path, 'r') as hook_file: hook_lines = hook_file.read() installed = 'hookmeup' in hook_lines hook_lines = hook_lines.splitlines() if installed: hook_lines = \ ['{}\n'.format(line) for line in hook_lines if line.find('hookmeup') == -1] with open(hook_path, 'w') as hook_file: hook_file.writelines(hook_lines) else: _print_msg('hookmeup not installed. nothing to do.') else: _print_msg('no hook to remove') PK!HgN%*)hookmeup-1.0.2.dist-info/entry_points.txtN+I/N.,()M--1r3PK:MdC-- hookmeup-1.0.2.dist-info/LICENSEMIT License Copyright (c) 2018, Daniel Moch Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. PK!Hd BUchookmeup-1.0.2.dist-info/WHEEL HM K-*ϳR03rOK-J,/RH,rzd&Y)r$[)T&UD"PK!H5x!hookmeup-1.0.2.dist-info/METADATAVmO#7_1=8URі"P8ɮMwƛR`ե@9Zaϝ-*Lmkҕq _Ļ=*cJ:ܫǙgJ69P>yeN9ɱy#HMrb;M6BG!½ᛇQ;Ff*Uf(=eRZfqsӞqfKOcd<(JW0ϥY FgH2Dr/*a4SoP7$ߵm|['AYF M\`:c3'uVWc/էX/<koHqp5 CY ڷo{\i=o Դ;T !))̖S0n$ Zw^.c&(񖴶T1aJB'8P:TC$m%JcVg~/{5P -=/~VPJ5sF|J-:Tt\7CU8:&4x 4eߤ`k5TRAc#'Ĥ9<ϒwK͖3PjPV[ 'f-fimpXi"[j*1_GtL3Q%:}j['~v'PUœ |W.<zYӿ+'$_PK!HUQphookmeup-1.0.2.dist-info/RECORD}v0}(?&&.`jԺPA @3Es-,yL^.IfPd>7'QM K