Posted at

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

More than 1 year has passed since last update.

これはNextremer Advent Calendar 2016の第2日目の記事です。


TL;DR



  • 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コマンドを使うことで、ローカルリポジトリの未プッシュのコミットをあれこれ編集することができます。幾つかのコミットをまとめて1つにしたり、コミットコメントを変更したり、特定のコミットを除外したり。。

こまめにコミットしながら開発を進めて、そろそろプッシュするかってタイミングで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


git rebase -iThere 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は、仮コミットを1コマンドでちゃちゃっとやってくれるツールです。

そろそろコミットしてぇなーってタイミングでgit nowと叩くだけでコミットしちゃってくれます。

まぁ普通にgit commit -am "適当なメッセージ"すりゃいいだけなんだけど、毎回コミットメッセージ書くのも面倒だし、タイプ数多いし。。。ってことでgit nowを使ってます。

導入も、MacユーザでHomebrew使ってるならbrew install git-nowでおkというお手軽さ。

これを導入することで、

git nowgit nowgit now→そろそろプッシュするか( ´_ゝ`)→git rebase -igit push

という流れで開発を進めることが出来ます(∩´∀`)∩ワーイ


git nowgit rebase -iが便利なのは分かったけど、間違えてプッシュしちゃうとヤバくね?


そこでPre-Push Hookですよ!

Gitには標準でHook機能がついてます。コミット前に○○したいとかプッシュしたら☓☓したいとかが出来るやつです。今回はPre-Push Hookを使って、プッシュ時にgit nowのコミットが残ってないかチェックしましょう。

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

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

pre-pushの内容は以下の通りです。

#!/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を使ってこまめにコミットしよう


  • git pushする前にgit rebase -iする癖つけよう

  • git-nowの仮コミットをそのままgit pushしちゃわないようにPre-Push Hook設定しよう