gitの共有リポジトリにpushしたらChatworkにメッセージを通知する

## 追記

[Slackにも通知できるようにした記事](http://qiita.com/artifactsauce/items/242c71428fa85307c182)を書きました。

## 目的

複数人でgitの共有リポジトリにpushするようになると、コンフリクトの可能性や同僚の進捗などが気になることがあります。その際には変更の度に何らかのお知らせが届くと嬉しいですよね。今回は[Chatwork](http://www.chatwork.com/ja/)を使ったpushの通知機能を実装してみます。

## 仕様

今回は下記のような状況を想定しています。

- gitを使ってソースコードを管理している。
- GitHubなどの外部サービスを使わずに、自前のサーバー中にgit共有リポジトリを置いている。
- Chatworkを利用している。

gitリポジトリに誰かがpushしたことを検知して、Chatworkの特定のチャットルームにコミット・メッセージを投稿します。あなたが管理しているリポジトリはおそらく1つや2つではないでしょうから、メッセージにはリポジトリ名とpushしたブランチ名が含まれるようにします。

## githooks

古くからバージョン管理システムには様々なタイミングでプログラムを実行させる機構、通称 *hooks* と呼ばれる仕組みが有りました。当然gitにもhooksの仕組みが備わっています。gitのhooksはクライアントサイドフックとサーバーサイドフックに大別され、今回の目的ではサーバーサイドフックを利用します。サーバーサイドフックには下記の4種類があります。

- `pre-recieve`
- `post-recieve`
- `pre-update`
- `post-update`

フック名はフックを掛けるアクションと実行されるタイミングで構成されています。上記のフック名では、 *pre* と *post* はそれぞれ該当するアクションの前後に実行されることを示しています。それでは *recieve* と *update* が示しているアクションは何なのでしょうか？ *recieve* と *update* は双方ともにpushされたタイミングで呼び出されます。その違いはPro Git Bookに端的に示されています。

> 複数のブランチへのプッシュがあったときに `pre-receive` が実行されるのは一度だけですが、update はブランチ単位でそれぞれ一度ずつ実行されます。

今回の仕様では、ブランチ名を取得してブランチごとに投稿する必要が有り、また特にpushを拒否したりする必要はないため、`post-update`を使用します。


### 参考

- [githooks(5) Manual Page](https://www.kernel.org/pub/software/scm/git/docs/githooks.html)
- [サーバーサイドフック](http://git-scm.com/book/ja/Git-%E3%81%AE%E3%82%AB%E3%82%B9%E3%82%BF%E3%83%9E%E3%82%A4%E3%82%BA-Git-%E3%83%95%E3%83%83%E3%82%AF#サーバーサイドフック)

## Chatwork API

Chatworkはクラウド型ビジネスチャットサービスで、複数人でのリアルタイムコミュニケーションを実現するためのツールです。また、[API Tokenを申請](http://developer.chatwork.com/ja/authenticate.html)すればWeb APIを利用することもでき、今回のような通知先として機能することができます。ただし、このAPIはまだベータ版という扱いらしく、申請してから許可が出るまで非常に時間がかかるので、思い立ったらすぐに申請しましょう。

### 参考

- [チャットワークAPIを限定プレビュー公開します！](http://blog-ja.chatwork.com/2013/11/api-preview.html)
- [チャットワークAPIドキュメント](http://developer.chatwork.com/ja/)

## post-update

今回の仕様を満たすようなhooksスクリプトを作成しました。下記のBashスクリプトを適当に修正して、通知対象としたいすべてのリモートリポジトリの `$REPOSITORY_ROOT/hooks/`に置きます。実行権限をつけることも忘れないでください。

```bash
#!/bin/bash

set -eu

API_TOKEN="<CHANGE_THIS>"
ROOM_ID="<CHANGE_THIS>"

REPOS=$(basename $(pwd))
REPOS=${REPOS%.*}

BRANCH=$(git rev-parse --symbolic --abbrev-ref $1)
MSG=$(git log -1 --pretty=format:"%h - %an : %s" $BRANCH)

BODY="(${REPOS}) [${BRANCH}] ${MSG}"
if [[ "$BRANCH" = 'master' ]]; then
  BODY="[info][title]PUSHED INTO \`master\` BRANCH!!![/title]${BODY}[/info]"
fi

curl -X POST -H "X-ChatWorkToken: ${API_TOKEN}" -d "body=${BODY}" "https://api.chatwork.com/v1/rooms/${ROOM_ID}/messages" > /dev/null 2>&1

exit 0
```

```bash
curl -LO https://gist.githubusercontent.com/artifactsauce/9480292/raw/post-update
vi post-update # 変数を修正
chmod 755 post-update
scp ./post-update $REMOTE_HOST:$REPOSITORY_ROOT/hooks/
```

`$REPOSITORY_ROOT`と`$REMOTE_HOST`はそれぞれ、リポジトリの最上位ディレクトリとリモートリポジトリを置いてあるホストの名前を示す任意の値とします。

次項以降でこのスクリプトの内容を解説します。

### 設定値

このスクリプトを正常に動作させるためには、冒頭の3つの変数の値を修正する必要があります。

まずはChatworkに投稿するための設定です。投稿ユーザーのAPIトークンと、投稿先のチャットルームのIDを設定します。

```bash
API_TOKEN="<CHANGE_THIS>"
ROOM_ID="<CHANGE_THIS>"
```

もう一つ、リポジトリ名を設定します。ハードコーディングしてありますが、上位ディレクトリの名前を動的に取得するという方法でも良いでしょう。

```bash
REPOS="<CHANGE_THIS>"
```

### ブランチ名の取得

post-updateは引数を1つ受け取ります。それはリモート側のリポジトリの参照名（例：`refs/heads/master`）です。まずはこの値からブランチ名を取得します。

```bash
BRANCH=$(git rev-parse --symbolic --abbrev-ref $1)
```

### メッセージの作成

取得した値からメッセージ・ボディーを作成します。メッセージはpushしたブランチの最後のコミットメッセージに、リポジトリ名およびブランチ名を付加したものとします。

```bash
MSG=$(git log -1 --pretty=format:"%h - %an : %s" $BRANCH)
BODY="(${REPOS}) [${BRANCH}] ${MSG}"
```

メッセージ・ボディーはもちろんご自由ですが、こんな感じになっていると嬉しいんじゃないでしょうか。`master`にpushしたときだけ、強調されるようにしてみましょう。

```bash
if [[ "$BRANCH" = 'master' ]]; then
  BODY="[info][title]PUSHED INTO \`master\` BRANCH!!![/title]${BODY}[/info]"
fi
```

### 投稿

最後にこの内容で[Web APIを通して投稿](http://developer.chatwork.com/ja/endpoint_rooms.html#POST-rooms-room_id-messages)します。

```
curl -X POST -H "X-ChatWorkToken: ${API_TOKEN}" -d "body=${BODY}" "https://api.chatwork.com/v1/rooms/${ROOM_ID}/messages" > /dev/null
```

標準出力の内容がpush時に表示されるので`/dev/null`に吐き出しておくことをお勧めします。

### 参考

- [git-rev-parse(1) Manual Page](https://www.kernel.org/pub/software/scm/git/docs/git-rev-parse.html)