LCL Engineers' Blog

バス比較なび・格安移動・バスとりっぷを運営する LCLの開発者ブログ

git hooksをovercommitで管理して作業効率の底上げを狙う

モバイルアプリエンジニアの山下です。

チームで開発を進める上でちょっとした"決まりごと"が存在すると思います。
例えば、LCLの開発チームには以下の"決まりごと"が存在します。

  • コミットメッセージの先頭にはYouTrackのIssue番号を付ける
  • RuboCopで設定したコーディングスタイルになるべく従う
  • masterへ直接PUSHはしない

これらを全員が心がけることで運用効率や品質を保つようにしていまが、とはいえコミットする度にIssue番号を入力したり、RuboCopのコマンドを打ったりするのは非常に面倒です。

そこで今回は「git hooks」を利用してコミットやプッシュのタイミングで自動的に実行されるようにしました。

git hooksとは

コミットやプッシュなどのgit操作をトリガーにしてスクリプトを実行する機能です。
.gitディレクトリ内にhooksという名前のディレクトリで管理されています。

例えば、コミット時にコミットメッセージに対して何かしらの処理を行うということができます。
これを利用することで手動で行っていた些細なことは自動化することができます。

しかし、git hooksは残念なことにgitで管理できません。(※ 別途、生成スクリプトを用意するなどの策はあります)
これではメンバー全員に設定してもらう必要がありますが、現実はそう浸透しません。

それを解決するためにovercommitを導入しました。

overcommitとは

https://github.com/brigade/overcommit

git hooksの管理・拡張をサポートするGemです。
.overcommit.ymlで実行するコマンドやスクリプトを管理します。
既存のgit hooksを使いまわすことも可能です。

また、あらかじめいくつかの設定が用意されています。

導入

Gitの操作と密に関係するため直接インストールします。

$ gem install overcommit

※ 既にgit hooksを利用している場合は、ファイルをバックアップしておきましょう。
overcommitをインストールする際に初期化される可能性があります。

overcommitをインストール

$ overcommit --install

それでは、さっそくコミットしてみましょう。恐らく以下の警告で失敗すると思います。

Runovercommit --signif you trust the hooks in this repository.

overcommitは、fetchしたymlやスクリプトが不正に書き換えられた場合に備えて、これらの差分が存在した際には署名コマンドを打つ必要があります。少し面倒ですが、安全面との引き換えには仕方がないです。

$ overcommit --sign

上記のコマンドを実行後に再度コミットをしてみましょう。
コミットメッセージのチェックに引っかからなければ、無事にコミットできると思います。

設定

前述の通り、デフォルト設定が存在し有効になっています。以下のコマンドで確認できます。

$ overcommit --list

以下のアスタリスク(*)が付いている項目がデフォルトで設定されている項目です。
https://github.com/brigade/overcommit#built-in-hooks

私の環境ではGitのユーザ名の制約を行う「AuthorName」で引っかかってしまい、コミットができませんでした。
ユーザ名は変えたくないので設定を無効化したいと思います。

.overcommit.ymlに以下の設定を追加します。

PreCommit:
  AuthorName:
    enabled: false

変更後は設定を反映させます。

$ overcommit --sign

これで無事にコミットできるようになりました。

RuboCopの実行

RuboCopには自動修正機能が存在します。これを実行するにはコマンドを打つ必要がありますが、今回はコミット時に毎回自動で実行するようにします。

実行したいコマンドは以下です。差分だけを対象にしています。

$ rubocop --auto-correct (git diff master --name-only)

overcommitはRuboCopを含む、様々なLintツールに対応しています。(インストールは別途必要です)
今回は対象ファイルの引数が必要なため、以下のようにcommandの項目を追加します。

PreCommit:
  RuboCop:
    enabled: true
    command: ['rubocop', '-auto-correct', '$(git diff master --name-only)']

変更後は設定を反映させます。

$ overcommit --sign

これでコミットする際に差分に対して自動でRuboCopの自動修正が実行されます。

コミットメッセージにYouTrackのIssue番号を追加

LCLではYouTrackでIssueを管理しており、ブランチ名やコミットメッセージには対応するYouTrack Issue番号を先頭に記述するようにしています。
YouTrackのIssue番号は以下のような構成になっています。

  • project-123
  • project_sub-123

前述の通り、毎回記述するのは手間がかかるため、ブランチ名を先頭に記述するスクリプトを作成しgit hooksで管理していました。
今回は、その hook を使いまわしたいと思います。

プロジェクトルート直下のbinディレクトリでhooksフォルダを作成し、更にcommit-msgファイルを作成します。
これからは独自のhookはhooksフォルダ内に追加していきます。

独自のhookを実行させるためには、実行権限を付与する必要があります。

$ chmod +x ./bin/hooks/commit-msg

commit-msgに今回の処理内容をを記載します。

#!/usr/bin/env ruby

# コミットメッセージを取得
msg_file = ARGV[0]
commit_msg = File.read(msg_file, encoding: Encoding::UTF_8)

# 最後の`/`以降の文字列を取得
youtrack_issue_number = `git branch | grep "*"`.sub(/^\*\s/, '').match(/([^\/]+)$/)[0].chomp

# YouTrack Issue番号を追加
commit_msg = "#{youtrack_issue_number} #{commit_msg}" unless commit_msg.include?(youtrack_issue_number)

# コミットメッセージを上書き
File.write(msg_file, commit_msg)

そして、overcommit.ymlに設定を追加します。

CommitMsg:
  CustomScript:
    enabled: true
    required_executable: './bin/hooks/commit-msg'

設定を反映させます。 今回はカスタムスクリプトなのでファイルを指定して署名する必要があります。

$ overcommit --sign commit-msg

以上で、コミットするとコミットメッセージの先頭にブランチ名が追加されると思います。
ブランチ名をIssue番号にすれば要件を満たすことができます。

masterへ直接PUSHできないようにする

先ほど作成したhooksディレクトリにpre-pushファイルを追加し、実行権限を付与します。

$ chmod +x ./bin/hooks/pre-push

ここではPUSH先がmasterを向いている場合、エラーメッセージを流すようにします。

#!/bin/sh

while read local_ref local_sha1 remote_ref remote_sha1
do
  if [[ "${remote_ref##refs/heads/}" = "master" ]]; then
    echo "masterへPUSHしないでください。"
    exit 1
  fi
done

overcommit.ymlに設定を追加します。

PrePush:
  CustomScript:
    enabled: true
    required_executable: './bin/hooks/pre-push'

設定を反映させます。

$  overcommit --sign commit-msg

masterへPUSHするとエラーメッセージが表示されれば成功です。

終わりに

Gitの操作は頻繁に行うので、同じことはなるべく自動化すると塵も積もって大きな時間短縮になると思います。 特に、自分だけで留めるのではなくチームを巻き込んで効率化することで効果は更に大きくなります。

今後は、画像圧縮や特定のファイルのコミット制限などの機能も追加していきたいと考えています。
ルーチンがある方は導入してみてはいかがでしょうか。