コードチェックを自動化するか、プリコミットフックについてもう少し詳しく

Git / GitHub、プリコミットが何であるか、そしてそれを右にフックする方法をユーザーに伝える必要はないと思います。 すぐにポイントを取得しましょう。



ネットワークには多くのフックの例があり、そのほとんどはシェル上にありますが、1人の重要な点に注意を払った著者は一人もいません。フックはプロジェクトからプロジェクトにドラッグする必要があります。 一見-大丈夫です。 しかし、突然20のプロジェクトに既に存在するフックに変更を加える必要があります...または、突然WindowsからLinuxに開発を移行し、フックをPowerShellに移行する必要があります...どうすればよいですか? ??????? 利益 ...



「そのような方が良い:8パイと1キャンドル!」



もちろん、例は非常に誇張されていますが、それらの助けを借りて、回避したい不便が確認されました。 フックをすべてのプロジェクトに沿ってドラッグしないようにします。頻繁に「仕上げる」必要はありませんでしたが、同時に次のこともできました。



そして、それは次のように見えました:

python pre-commit.py --check pep8.py --test tests.py
      
      





フック自体が単なるスターターであり、すべての特別なストリートマジックはそれが起動するスクリプトによって実行されることは明らかです。 そのようなスクリプトを書きましょう。 興味があれば、猫にようこそ。



pre-commit.py



ただし、完成したサンプルをネットワークからダウンロードして開発開始する前にそれらを取得するパラメーターを検討してください。 同時に、例を使用してすべてがどのように機能するかを説明します。



これらのパラメーターは、スクリプトの主な動作を定義します。



両方のパラメーターはオプションです(1種類のチェックのみを残す可能性のため)が、どちらも指定しない場合、 pre-commit.pyはコード「1」(エラー)で終了します。



そして、補助パラメーターを追加します(すべてオプション):



ロジックは明確です。スクリプト自体の作成を開始できます。



コマンドラインオプション



まず、コマンドラインパラメーターパーサーを構成します。 ここでは、ベースPythonパッケージに含まれているため、 argparseモジュールを使用します(または「指で」については、 ここここで 詳しく説明します )。

 # -*- coding: utf-8 -*- import sys import argparse #    parser = argparse.ArgumentParser() #   .   , #    :   1-N  parser.add_argument('-c', '--check', nargs='+') #   --check parser.add_argument('-t', '--test', nargs='+') #  -.  ,     # True.    - False parser.add_argument('-v', '--verbose', action='store_true') #     . #    - =default parser.add_argument('-e', '--exec', default=sys.executable) #     . #    - =None parser.add_argument('-o', '--openlog') #   --verbose parser.add_argument('-f', '--forcelog', action='store_true') #  1-  (  ),  #       dict params = vars(parser.parse_args(sys.argv[1:]))
      
      





次のパラメーターを使用してスクリプトを実行します。

 c:\python34\python c:\dev\projects\pre-commit-tool\pre-commit.py --check c:\dev\projects\pre-commit-tool\pep8.py --test tests.py
      
      





そして、画面にparamsの内容を表示します:

 {'exec': 'c:\\python34\\python.exe', 'forcelog': False, 'test': ['tests.py'], 'check': ['c:\\dev\\projects\\pre-commit-tool\\pep8.py'], 'openlog': None, 'verbose': False}
      
      





これで、すべてのパラメーターの値がparams辞書にあり、同じ名前のキーで簡単に取得できます。

メインパラメータの存在のチェックを追加します。

 #         if params.get('check') is None and params.get('test') is None: print('   ') exit(1)
      
      





すべては問題ありませんが、柔軟性を損なうことなく、生活を少し簡素化できます。 99%のケースで、検証スクリプトは1つであり、たとえば 'pep8.py'と呼ばれ、権限内のユニットテストスクリプトは毎回同じものが呼び出されます(多くの場合、1つでもあります)。 同様に、ログ表示の場合-常に同じプログラムを使用します(メモ帳にします)。 パーサーの設定を変更しましょう:

 #       0-N  parser.add_argument('-c', '--check', nargs='*') parser.add_argument('-t', '--test', nargs='*') #     ,     const parser.add_argument('-o', '--openlog', nargs='?', const='notepad')
      
      





そして、デフォルト設定を追加します。

 if params.get('check') is not None and len(params.get('check')) == 0: #     ,   pre-commit.py params['check'] = [join(dirname(abspath(__file__)), 'pep8.py')] if params.get('test') is not None and len(params.get('test')) == 0: params['test'] = ['tests.py']
      
      





変更後、パーサー構成コードは次のようになります。

 # -*- coding: utf-8 -*- import sys import argparse from os.path import abspath, dirname, join parser = argparse.ArgumentParser() parser.add_argument('-c', '--check', nargs='*') parser.add_argument('-t', '--test', nargs='*') parser.add_argument('-v', '--verbose', action='store_true') parser.add_argument('-e', '--exec', default=sys.executable) parser.add_argument('-o', '--openlog', nargs='?', const='notepad') parser.add_argument('-f', '--forcelog', action='store_true') params = vars(parser.parse_args(sys.argv[1:])) if params.get('check') is None and params.get('test') is None: print('   ') exit(1) if params.get('check') is not None and len(params.get('check')) == 0: params['check'] = [join(dirname(abspath(__file__)), 'pep8.py')] if params.get('test') is not None and len(params.get('test')) == 0: params['test'] = ['tests.py']
      
      





これで、スクリプトの起動ラインが短くなりました。

 c:\python34\python c:\dev\projects\pre-commit-tool\pre-commit.py --check --test --openlog
      
      



パラメータの内容:

 {'check': ['c:\\dev\\projects\\pre-commit-tool\\pep8.py'], 'openlog': 'notepad', 'test': ['tests.py'], 'verbose': False, 'exec': 'c:\\python34\\python.exe', 'forcelog': False}
      
      





パラメータが獲得されました。



ログ



ログオブジェクトを設定します。 ログファイル「pre-commit.log」は、現在のプロジェクトのルートに作成されます。 Gitの場合、作業ディレクトリはプロジェクトのルートであるため、ファイルへのパスは指定しません。 また、操作ごとに新しいファイルを作成するモードを示し(以前のログを保存する必要はありません)、ログの形式を設定します-メッセージのみ:

 import logging log_filename = 'pre-commit.log' logging.basicConfig( filename=log_filename, filemode='w', format='%(message)s', level=logging.INFO) to_log = logging.info
      
      





コードの最後の行は、私たちの生活を少し簡略化します。logging.infoの代わりにコードでさらに使用するエイリアスを作成します。



シェル



子プロセスを繰り返し起動し、その出力をコンソールに読み込む必要があります。 このニーズを実装するために、 shell_command関数を作成します。 彼女の責任は次のとおりです。



関数は引数を取ります:



 from subprocess import Popen, PIPE def shell_command(command, force_report=None): #   proc = Popen(command, stdout=PIPE, stderr=PIPE) #    proc.wait() #     # (  ,  "\r\n") transform = lambda x: ' '.join(x.decode('utf-8').split()) #  ( )  stdout report = [transform(x) for x in proc.stdout] #   stderr report.extend([transform(x) for x in proc.stderr]) #        force_report if force_report is True or (force_report is not None and proc.returncode > 0): to_log('[ SHELL ] %s (code: %d):\n%s\n' % (' '.join(command), proc.returncode, '\n'.join(report))) #           return proc.returncode, report
      
      





頭の修正



現在のコミットのファイルのリストは、 コンソールコマンドGit-"diff"を使用して簡単に取得できます。 この場合、変更されたファイルまたは新しいファイルが必要です。

 from os.path import basename #     result_code = 0 #     commit' code, report = shell_command( ['git', 'diff', '--cached', '--name-only', '--diff-filter=ACM'], params.get('verbose')) if code != 0: result_code = code #     "py" targets = filter(lambda x: x.split('.')[-1] == "py", report) #     (  ) targets = [join(dirname(abspath(x)), basename(x)) for x in targets]
      
      





その結果、 ターゲットには次のようなものが含まれます。

 ['C:\\dev\\projects\\example\\demo\\daemon_example.py', 'C:\\dev\\projects\\example\\main.py', 'C:\\dev\\projects\\example\\test.py', 'C:\\dev\\projects\\example\\test2.py']
      
      





最も痛い段階が完了しました-それはさらに簡単になります。



検証チェック



ここではすべてが簡単です。--checkで指定されたすべてのスクリプトを実行し、 ターゲットのリストを使用してそれぞれを実行します。

 if params.get('check') is not None: for script in params.get('check'): code, report = shell_command( [params.get('exec'), script] + targets, params.get('verbose')) if code != 0: result_code = code
      
      





検証チェックに合格しなかったコードのログの内容の例:

[ SHELL ] C:\python34\python.exe c:\dev\projects\pre-commit-tool\pep8.py C:\dev\projects\example\demo\daemon_example.py (code: 1):

C:\dev\projects\example\demo\daemon_example.py:8:80: E501 line too long (80 > 79 characters)







テストを実行する



単体テストでも同じことを行いますが、 ターゲットはありません。

 if params.get('test') is not None: for script in params.get('test'): code, report = shell_command( [params.get('exec'), script], params.get('verbose')) if code != 0: result_code = code
      
      





[UPD]ログを表示します



グローバル結果コードと--openlogおよび--forcelogパラメーターに応じて、ログを表示するかどうかを決定します。

 if params.get('openlog') and (result_code > 0 or params.get('forcelog')): #    Popen([params.get('openlog'), log_filename], close_fds=True)
      
      





ご注意 Python 2.6(以降)および3.xのバージョンで動作します。 2.6より前のバージョン-テストは実行されませんでした



また、スクリプトの最後で結果コードをGitシェルに返すことを忘れないでください。

 exit(result_code)
      
      





それだけです スクリプトを使用する準備ができました。



悪の根



フックとは、「pre-commit」と呼ばれるファイル(拡張子なし)で、ディレクトリ<project_dir> /。Git / hooks /に作成する必要があります。



Windowsで正しく実行するための重要なポイントがいくつかあります。

1.ファイルの最初の行は#!/ Bin / shです

そうでない場合、次のエラーが表示されます。

GitHub.IO.ProcessException: error: cannot spawn .git/hooks/pre-commit: No such file or directory







2.パスを指定するときに標準の区切り文字を使用すると、同様のエラーが発生します。

GitHub.IO.ProcessException: C:\python34\python.exe: can't open file 'c:devprojectspre-commit-toolpre-commit.py': [Errno 2] No such file or directory







これは3つの方法で処理できます。二重バックスラッシュを使用するか、二重引用符を使用するか、「/」を使用します。 たとえば、Windowsはこれを食べて窒息しません。

 #!/bin/sh c:/python34/python "c:\dev\projects\pre-commit-tool\pre-commit.py" -c -tc:\\dev\\projects\\example\\test.py
      
      





もちろん、これはお勧めできません:)好きな方法を使用してください。



受入試験



「猫で」トレーニングします。



画像



テストコミットには、新しい名前が変更された\変更および削除されたファイルがあります。 また、コードを含まないファイルも含まれます。 コード自体に設計エラーが含まれており、単体テストの1つに合格しません。 検証、テスト、詳細ログを開くフックを作成しましょう:

 c:/python34/python c:/dev/projects/pre-commit-tool/pre-commit.py -c -t test.py test2.py -vfo
      
      





そして、コミットを実行してみてください。 数秒後、 Gitデスクトップはエラー通知します:



画像



そして次のウィンドウで、メモ帳には次のように表示されます。



[ SHELL ] git diff --cached --name-only --diff-filter=ACM (code: 0):

.gitattributes1

demo/daemon_example.py

main.py

test.py

test2.py



[ SHELL ] C:\python34\python.exe c:\dev\projects\pre-commit-tool\pep8.py C:\dev\projects\example\demo\daemon_example.py C:\dev\projects\example\main.py C:\dev\projects\example\test.py C:\dev\projects\example\test2.py (code: 1):

C:\dev\projects\example\demo\daemon_example.py:8:80: E501 line too long (80 > 79 characters)

C:\dev\projects\example\demo\daemon_example.py:16:5: E303 too many blank lines (2)

C:\dev\projects\example\demo\daemon_example.py:37:5: E303 too many blank lines (2)

C:\dev\projects\example\demo\daemon_example.py:47:5: E303 too many blank lines (2)

C:\dev\projects\example\main.py:46:80: E501 line too long (90 > 79 characters)

C:\dev\projects\example\main.py:59:80: E501 line too long (100 > 79 characters)

C:\dev\projects\example\main.py:63:80: E501 line too long (115 > 79 characters)

C:\dev\projects\example\main.py:69:80: E501 line too long (105 > 79 characters)

C:\dev\projects\example\main.py:98:80: E501 line too long (99 > 79 characters)

C:\dev\projects\example\main.py:115:80: E501 line too long (109 > 79 characters)

C:\dev\projects\example\main.py:120:80: E501 line too long (102 > 79 characters)

C:\dev\projects\example\main.py:123:80: E501 line too long (100 > 79 characters)



[ SHELL ] C:\python34\python.exe test.py (code: 1):

Test 1 - passed

Test 2 - passed

[!] Test 3 FAILED



[ SHELL ] C:\python34\python.exe test2.py (code: 0):

Test 1 - passed

Test 2 - passed







詳細なログなしでのみ、同じコミットを繰り返します。

 c:/python34/python c:/dev/projects/pre-commit-tool/pre-commit.py -c -t test.py test2.py -fo
      
      





結果:



[ SHELL ] C:\python34\python.exe c:\dev\projects\pre-commit-tool\pep8.py C:\dev\projects\example\demo\daemon_example.py C:\dev\projects\example\main.py C:\dev\projects\example\test.py C:\dev\projects\example\test2.py (code: 1):

C:\dev\projects\example\demo\daemon_example.py:8:80: E501 line too long (80 > 79 characters)

C:\dev\projects\example\demo\daemon_example.py:16:5: E303 too many blank lines (2)

C:\dev\projects\example\demo\daemon_example.py:37:5: E303 too many blank lines (2)

C:\dev\projects\example\demo\daemon_example.py:47:5: E303 too many blank lines (2)

C:\dev\projects\example\main.py:46:80: E501 line too long (90 > 79 characters)

C:\dev\projects\example\main.py:59:80: E501 line too long (100 > 79 characters)

C:\dev\projects\example\main.py:63:80: E501 line too long (115 > 79 characters)

C:\dev\projects\example\main.py:69:80: E501 line too long (105 > 79 characters)

C:\dev\projects\example\main.py:98:80: E501 line too long (99 > 79 characters)

C:\dev\projects\example\main.py:115:80: E501 line too long (109 > 79 characters)

C:\dev\projects\example\main.py:120:80: E501 line too long (102 > 79 characters)

C:\dev\projects\example\main.py:123:80: E501 line too long (100 > 79 characters)



[ SHELL ] C:\python34\python.exe test.py (code: 1):

Test 1 - passed

Test 2 - passed

[!] Test 3 FAILED









エラーを修正し、コミットを繰り返します。ここに待望の結果があります。Git デスクトップは誓いません。メモ帳には空のpre-commit.logが表示されます。 利益。



ここで完成した例を見ることができます



[UPD]結論の代わりに



もちろん、このスクリプトは万能薬ではありません。 必要なすべてのチェックがテストスクリプトをローカルで実行することに限定されている場合に役立ちます。 複雑なプロジェクトでは、 継続的インテグレーション(またはCI)の概念が通常使用されますが、ここではTravis (LinuxおよびOS X用)とそのアナログAppVeyor (Windows用)が助けになります。



[UPD]別の選択肢は、 オーバーコミットです。 Gitフックを管理するための非常に機能的なツール。 ただし、微妙な違いがあります- オーバーコミットを機能させるには、 Rubyインタープリターをローカルにデプロイする必要があります。



適切なコーディングと正しいコミットを行います。



All Articles