いまや Git なしでは生きていけないくらい Git に支えられている生活を送っていますが、Git のブランチをマージする手段にはいろいろあって、実際 GitHub でもプルリクエストをマージするときには 3 種類の方法から選ぶよう促されます。
git merge
ブランチをマージするコマンドですが、ブランチの状況によって挙動が変わります。具体的には Fast Forward と non-Fast Forward の 2 種類があり、オプションで指定しない限りは Git が勝手に判断してくれます。ここでは、master
から派生したtopic/a
ブランチをmaster
にマージすることを想定したときの動きを説明します。
Fast Forward
topic/a
の派生元のリビジョンがmaster
の最新リビジョンと一致するときは、単純にtopic/a
の最新リビジョンをmaster
の最新リビジョンに変えるだけでブランチがマージできたことになります。このように、マージ先ブランチの最新リビジョンをマージ元ブランチの最新リビジョンにするだけでマージが完了することを Fast Forward といいます。
git log
で歴史を見ると、マージされたtopic/a
ブランチはmaster
の歴史の一部となっていて、別のブランチが存在したようには見えなくなります。
強制的に Fast Forward でマージする場合は git merge --ff-only
とします(Fast Forward とならない場合はエラーとしてマージできない)が、オプションなしで git merge
する場合のデフォルトの挙動も Fast Forward です(Fast Forward とならない場合は non-Fast Forward でマージしようとする)。
non-Fast Forward
topic/a
を派生したあとにmaster
に異なるコミットがある場合は、master
の最新リビジョンを変えてしまうとtopic/a
を派生したあとに積み上げたコミットが無かったことになってしまうため、Fast Forward でマージできません。そこで、topic/a
にあるコミットをmaster
に混ぜた上で、マージをしたことを示すマージコミットを作成します。
git log
で歴史を見ると、マージされたtopic/a
ブランチの持っていた歴史は時間順にmaster
の歴史に取り込まれつつ、マージコミットによって別のブランチが存在していたこともわかるようになります。
強制的に non-Fast Forward でマージする場合は git merge --no-ff
とします(Fast Forward でマージ可能な場合でも non-Fast Forward でマージコミットを作る)。
Squash
これは上記の 2 つとは異なり、明示的にオプションで指定(--squash)しない限りこの挙動にはなりません。
topic/a
にある全コミットを1つに集約しmaster
にコミットを作るマージのことを Squash マージといいます。git log
で歴史を見ると、Fast Forward のようにtopic/a
がmaster
の歴史の一部になったようには見えなくなります。Squash マージしたあとでさらにtopic/a
にコミットを積み上げ Squash マージをすると、もういちど topic/a
の全コミットを1つに集約しなおしてコミットを作ろうとします。
余談
git rebase
なにかと怖がられがちですが、してはいけないこと*1はとてもシンプルかつたいていどこかでエラーを起こして失敗する*2ので、もっと安心して使えるコマンドです。 GitHub のプルリクエストをマージするときの選択肢に出てくるうちの1つでもあります。
名前のとおり、ブランチの派生元リビジョンを変える(re base)ためのコマンドで、手作業で同等のコマンドを打つとするなら、ブランチを目的の派生元リビジョンから切りなおしたうえで rebase 前のブランチにあるコミットを1つずつ cherry-pick するようなものです。たとえば、master
の歴史とtopic/a
の歴史がそれぞれに進んでいるときにtopic/a
をmaster
に rebase すると、topic/a
の派生元はmaster
の最新リビジョンとなり、topic/a
にあったコミットは別のリビジョン番号を持って積み直されます。
git pull
リモートリポジトリからコミットを取得するコマンドです。デフォルトでは git fetch
かつ git merge
の動きをしますが、オプション(--rebase
)をつけると git fetch
かつ git rebase
の動きをします。
たとえば、master
で git pull
をすると、Fast Forward できるときはマージコミットは作られず、non-Fast Forward の場合はマージコミットができます。また master
で git pull --rebase
をする場合、一旦リモートリポジトリのmaster
を取り込んだ上で、手元のmaster
に積み上がっていたコミットを積み直します。
merge や rebase を取り消したくなったとき
ちなみに、merge や rebase が仮にうまく完了したあとでそれらを取り消したくなった場合、リモートリポジトリに push していなければ*3、git reset --hard ORIG_HEAD
でコマンドを打つ前の状態に戻れます。