Git nowではじめるCommit駆動開発

これは[Nextremer Advent Calendar 2016](http://qiita.com/advent-calendar/2016/nextremer)の第2日目の記事です。

# TL;DR
* [git-now](https://github.com/iwata/git-now)を使ってこまめにコミットしよう
* `git push`する前に`git rebase -i`する癖つけよう
* git-nowの仮コミットをそのまま`git push`しちゃわないようにPre-Push Hook設定しよう

# はじめに
Gitを使う上で誰しもが悩むであろうことが、`どの粒度でCommit & Pushするか`だと思います。

こまめにプッシュするとログがとんでもないことになるし。。。

かと言ってある程度大きな単位でプッシュするようにするとローカルの差分ファイルの量がとんでもないことになるし。。。

# 基本的にはこまめにコミットしたほうがいい

細かくcommitすることで、以下のメリットがあります。

* 作業内容のスナップショット的に使える（ミスっても上手くいったところまで簡単に戻せる）
* 作業内容が失われにくい（間違ってreset --hard HEAD叩いちゃっても無問題）
* 作業の流れを追いやすい（なんかミスったときとか。コミットしてないといろんな修正がごった煮になってて自分のコードのはずなのに追えなかったり）
* 急に他の作業が入っても切り替えが簡単
  * git stashだと作業内容は保存できるけどstashするファイルを選べない（選べなくはないけど面倒
  * git commitしちゃえば、必要なファイルだけcommit出来る
* （ミスらなければ）ローカルリポジトリの中だけの話なのでコミットグラフは汚さない

もちろん、メリットがあればデメリットもあります

* ミスってpushしたら悲惨（後述するけど、コミットメッセージが適当なことがほとんど
* 毎度毎度commitするのが面倒

# こまめにコミットしたほうがいいのは分かったけど、プッシュする時はコミットまとめたいんだけど・・・
## そこで`git rebase -i`ですよ！
`git rebase -i`コマンドを使うことで、ローカルリポジトリの未プッシュのコミットをあれこれ編集することができます。幾つかのコミットをまとめて１つにしたり、コミットコメントを変更したり、特定のコミットを除外したり。。

こまめにコミットしながら開発を進めて、そろそろプッシュするかってタイミングで`git rebase -i`を叩いてみましょう。

```
pick <commit id #1> 1回目のコミット
pick <commit id #2> 2回目のコミット
pick <commit id #3> 3回目のコミット
pick <commit id #4> 4回目のコミット
pick <commit id #5> 5回目のコミット

# Rebase ...
#
# Commands:
#  p, pick = use commit
#  r, reword = use commit, but edit the commit message
#  e, edit = use commit, but stop for amending
#  s, squash = use commit, but meld into previous commit
#  f, fixup = like "squash", but discard this commit's log message
#  x, exec = run command (the rest of the line) using shell
#
# If you remove a line here THAT COMMIT WILL BE LOST.
# However, if you remove everything, the rebase will be aborted.
```

こんな画面が出ます。未プッシュのコミットがずら～っと並んでいて、全て`pick`になっています。下部のヘルプにも書いていますが、`pick`は**そのコミットをそのまま使う**という意味です。`pick`の部分を変更することでコミットを操作することが出来ます。よく使うのだと、以下のものがあります。

* `p`: コミットを使う
* `r`： コミットコメントを変更する
* `s`： 前のコミットにまとめる（コミットコメント変更可能）
* `f`： 前のコミットにまとめる（コミットコメント変更しない）

例えば、以下のように修正してみましょう。これで、**1〜3のコミットをまとめてコミットコメントを変更。4〜5のコミットをまとめてコミットコメントを変更**という意味になります。

```
r <commit id #1> 1回目のコミット
f <commit id #2> 2回目のコミット
f <commit id #3> 3回目のコミット
r <commit id #4> 4回目のコミット
f <commit id #5> 5回目のコミット
```

コミットコメントを変更する場合は、↑の`rebase -i`で起動したエディタを保存して閉じた後に再度エディタが起動します。コミットコメントを入力して保存しましょう。

例：
![rebase-compressor.gif](https://qiita-image-store.s3.amazonaws.com/0/16629/a4fb05e5-d576-2bbc-a9dd-a3d1dede93d8.gif)


### ※ `git rebase -i`で`There is no tracking information for the current branch.`エラーが出た場合

`git rebase -i`を実行すると、以下のようなエラーが表示されることがあります。

```
There is no tracking information for the current branch.
Please specify which branch you want to rebase against.
See git-rebase(1) for details.

    git rebase <branch>

If you wish to set tracking information for this branch you can do so with:

    git branch --set-upstream-to=<remote>/<branch> <your branch>
```

このエラーが表示されたら、エラーメッセージにも書いていますが、`git branch --set-upstream-to=`を実行しましょう。

例：`git branch --set-upstream-to=origin/topic-bugfix topic-bugfix`

`--set-upstream-to=<remote>/<branch>`はプッシュするリモートブランチを指定すれば良いですが、リモートリポジトリに存在しないブランチ（まだ一度もプッシュしていないブランチ）で`rebase`する場合は、そのブランチの分岐元となるブランチを指定すればOKです。
 

# `rebase`の使い方は分かったけど、こまめにコミットするのめんどくね？
## そこで`git-now`ですよ！
[git-now](https://github.com/iwata/git-now)は、仮コミットを1コマンドでちゃちゃっとやってくれるツールです。
そろそろコミットしてぇなーってタイミングで`git now`と叩くだけでコミットしちゃってくれます。

まぁ普通に`git commit -am "適当なメッセージ"`すりゃいいだけなんだけど、毎回コミットメッセージ書くのも面倒だし、タイプ数多いし。。。ってことで`git now`を使ってます。
導入も、MacユーザでHomebrew使ってるなら`brew install git-now`でおｋというお手軽さ。

これを導入することで、
`git now`→`git now`→`git now`→そろそろプッシュするか(　´_ゝ｀)→`git rebase -i`→`git push`
という流れで開発を進めることが出来ます(∩´∀｀)∩ﾜｰｲ

# `git now`→`git rebase -i`が便利なのは分かったけど、間違えてプッシュしちゃうとヤバくね？
## そこで`Pre-Push Hook`ですよ！
Gitには標準でHook機能がついてます。`コミット前に○○したい`とか`プッシュしたら☓☓したい`とかが出来るやつです。今回は`Pre-Push Hook`を使って、**プッシュ時に`git now`のコミットが残ってないかチェック**しましょう。

Git Hookは各Gitプロジェクトの`.git/hooks/`の下に生成されています。そこに直接書き込んでもいいのですが　、全てのプロジェクトで変更するのも面倒なので、`git init`や`git clone`したら自分のHookが生成されるように設定しちゃいましょう。

`~/.git_template/hooks/`ディレクトリを作成し、その下に`pre-push`というファイルを作成します。
`pre-push`の内容は以下の通りです。

```sh
#!/bin/sh

# An example hook script to verify what is about to be pushed.  Called by "git
# push" after it has checked the remote status, but before anything has been
# pushed.  If this script exits with a non-zero status nothing will be pushed.
#
# This hook is called with the following parameters:
#
# $1 -- Name of the remote to which the push is being done
# $2 -- URL to which the push is being done
#
# If pushing without using a named remote those arguments will be equal.
#
# Information about the commits which are being pushed is supplied as lines to
# the standard input in the form:
#
#   <local ref> <local sha1> <remote ref> <remote sha1>
#
# This sample shows how to prevent push of commits where the log message starts
# with "WIP" (work in progress).

remote="$1"
url="$2"

z40=0000000000000000000000000000000000000000

while read local_ref local_sha remote_ref remote_sha
do
	if [ "$local_sha" = $z40 ]
	then
		# Handle delete
		:
	else
		if [ "$remote_sha" = $z40 ]
		then
			# New branch, examine all commits
			range="$local_sha"
		else
			# Update to existing branch, examine new commits
			range="$remote_sha..$local_sha"
		fi

		# Check for WIP commit
		commit=`git rev-list -n 1 --grep '^\[from now\]' "$range"`
		if [ -n "$commit" ]
		then
			echo >&2 "Found NOW commit in $local_ref, not pushing"
			exit 1
		fi
	fi
done

exit 0
```
簡単に言うと、コミットコメントに`[from now]`が含まれている場合はプッシュできないようにしています。
これで`git now`コミットで`rebase`漏れのコミットが残っている場合にはプッシュできなくなり、誤爆することがなくなります(∩´∀｀)∩ﾜｰｲ

# まとめ（TL;DRと一緒
* [git-now](https://github.com/iwata/git-now)を使ってこまめにコミットしよう
* `git push`する前に`git rebase -i`する癖つけよう
* git-nowの仮コミットをそのまま`git push`しちゃわないようにPre-Push Hook設定しよう