これは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
で起動したエディタを保存して閉じた後に再度エディタが起動します。コミットコメントを入力して保存しましょう。
※ 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は、仮コミットを1コマンドでちゃちゃっとやってくれるツールです。
そろそろコミットしてぇなーってタイミングでgit now
と叩くだけでコミットしちゃってくれます。
まぁ普通にgit commit -am "適当なメッセージ"
すりゃいいだけなんだけど、毎回コミットメッセージ書くのも面倒だし、タイプ数多いし。。。ってことでgit now
を使ってます。
導入も、MacユーザでHomebrew使ってるならbrew install git-now
でおkというお手軽さ。
これを導入することで、
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
の内容は以下の通りです。
#!/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設定しよう