Git
grunt

Gitフックを仕込むgrunt-githooksについて紹介するよ

More than 5 years have passed since last update.


本投稿について

Gruntプラグインを紹介していくGrunt Plugins Advent Calendar 2013の12/1の投稿です。

さっき作ったばかりなので、まだすっきりさっぱりしたカレンダーになってますので、どうぞよろしくお願いします。

http://qiita.com/advent-calendar/2013/grunt-plugins


grunt-githooksとは

GitフックでGruntタスクを実行する仕込みをしてくれるGruntプラグインです。

https://npmjs.org/package/grunt-githooks

https://github.com/rhumaric/grunt-githooks


Gitフックとは

Gitではcommitやpush, rebase, checkoutなどいくつかの操作ができますが、それらの特定の操作を行ったときに、スクリプトを実行することができる仕組みがあり、それをGitフックと呼びます。


どんな種類のフックがあるのか

まずはソースコードをGitバージョン管理するために、git initで初期化してください。

% git init

すると、カレントディレクトリに.gitディレクトリが作られますが、その中の.git/hooksディレクトリにGitのクライアントサイドフックのサンプルが提供されています。

% tree -L 3 -a

.
|-- .git
| |-- HEAD
| |-- config
| |-- description
| |-- hooks
| | |-- applypatch-msg.sample
| | |-- commit-msg.sample
| | |-- post-update.sample
| | |-- pre-applypatch.sample
| | |-- pre-commit.sample
| | |-- pre-push.sample
| | |-- pre-rebase.sample
| | |-- prepare-commit-msg.sample
| | `-- update.sample
| |-- info
| | `-- exclude
| |-- objects
| | |-- info
| | `-- pack
| `-- refs
| |-- heads
| `— tags

ファイル名を見ると想像できるように、pre-commit.samplecommitする直前にフックされるスクリプトのサンプル、pre-push.samplepushする直前にフックされるスクリプトのサンプル(Git 1.8.2以降対応)になります。

pre-commitpre-pushという.sampleを除いた名前のファイルを作ると、commitpushの各操作から呼ばれます。


導入


インストール

npm installでgrunt-githooksをインストールします。package.jsonのdevDependenciesに追加されるように--save-devオプションを忘れずに。

% npm install grunt-githooks --save-dev


Gruntfile.js

grunt-githooks タスクをロードするように追記し、githooksタスクを定義します。

以下はシェルスクリプトのテンプレートを使ったタスク定義の例です。

他にもtemplateにはNodeで定義できるnode_modules/grunt-githooks/templates/node.js.hbテンプレートを指定可能です。

module.exports = function(grunt) {

grunt.initConfig({
githooks: {
options: {
dest: '.git/hooks', //省略可
hashbang: '#!/bin/sh',
template: './node_modules/grunt-githooks/templates/shell.hb',
startMarker: '## GRUNT-GRUNTHOOKS START',
endMarker: '## GRUNT-GRUNTHOOKS END'
},
setup: {
'pre-commit': 'git-pre-commit'
}
}
});
grunt.registerTask('git-pre-commit', [“jshint"]); // jshintタスクの定義は省略
grunt.loadNpmTasks('grunt-githooks');
};


実行

% grunt githooks

Running "githooks:setup" (githooks) task

Binding `git-pre-commit` to `pre-commit` Git hook.
OK

Done, without errors.

% tree -L 3 -a
.
|-- .git
| |-- HEAD
| |-- config
| |-- description
| |-- hooks
| | |-- applypatch-msg.sample
| | |-- commit-msg.sample
| | |-- post-update.sample
| | |-- pre-applypatch.sample
| | |-- pre-commit
| | |-- pre-commit.sample
| | |-- pre-push.sample
| | |-- pre-rebase.sample
| | |-- prepare-commit-msg.sample
| | `-- update.sample
| |-- info
| | `-- exclude
| |-- objects
| | |-- info
| | `-- pack
| `-- refs
| |-- heads
| `— tags

すると.git/hooks/pre-commit ファイルが生成されています。

% less .git/hooks/pre-commit

#!/bin/sh
## GRUNT-GRUNTHOOKS START
(cd /Users/shoito/workspaces/gac2013 && grunt git-pre-commit)

## GRUNT-GRUNTHOOKS END

pre-commitフックが仕込まれたので、さっそくgit commitして動作確認してみます。

% git commit

Running "jshint:gruntfile" (jshint) task
>> 1 file lint free.

Running "jshint:lib_test" (jshint) task
Linting lib/htmlparser.js ...ERROR
[L65:C35] W116: Expected '===' and instead saw '=='.
if ( html.indexOf("<!--") == 0 ) {
[L70:C15] W116: Expected '{' and instead saw 'handler'.

handler.comment( html.substring( 4, index ) );

(略)
Warning: Task "jshint:lib_test" failed. Use --force to continue.

Aborted due to warnings.

このケースでは、git commitしたがpre-commitフックで呼ばれるGruntタスクのjshintタスクによるコーディングルールのチェックをパスできず、git commitが失敗に終わりました。

警告に従い、コードを修正した後に再度git commitしましょう。


まとめ

このように、grunt-githooksプラグインを使い、チーム内で共通のGitフックを仕込んでおくことができます。

例えば、pre-commitフックにコーディングルールのチェックやテストのGruntタスクが呼ばれるように仕込んでおくと「誰だよ、テスト通ってないコードをpushしたやつは!?」という事態が防止できます。


Tips

コーディングルールのチェックやテスト(軽いもの)のGruntタスクの実行はファイル変更の監視を行っているwatchタスクに仕込んでおいて、PhantomJS/CasperJSなどを使ったUIテストのタスク(重いもの)はpre-commitフックから呼び出すようにしておくなどすると良いと思います。


Q&A

Q. なぜ、githooksタスクからわざわざgit-pre-commitを呼ぶようにしているか?

    setup: {

'pre-commit': 'git-pre-commit'
}
(略)
grunt.registerTask('git-pre-commit', ['jshint']);

A. このようにしておくと、pre-commitタスクで実行したいGruntタスクに変更があっても、Gruntfile.jsの定義を更新すればよく、grunt githooksを実行して.git/hooks/pre-commitを更新する必要がないためです。この例のjshintだと軽すぎますが、うちではcasperjsが含まれた定義になってます。


Appendix

Grunt Plugins

http://gruntjs.com/plugins