git-mergeで--ff(デフォルト)と--no-ffと--squashの違い

きっかけ

A successful Git branching model » nvie.com
ここらあたりを見て、--no-ffつけときゃいいんだなーと深く考えずに使ってたら、--squashつけると安全とかなんとかチラ聞きして混乱しまくったので調べてみた。
とりあえずman。

man git-merge
...
--ff, --no-ff
Do not generate a merge commit if the merge resolved as a fast-forward, only update the branch pointer. This is the default behavior of git-merge.
With --no-ff Generate a merge commit even if the merge resolved as a fast-forward.
...
--squash, --no-squash
Produce the working tree and index state as if a real merge happened (except for the merge information), but do not
actually make a commit or move the HEAD, nor record $GIT_DIR/MERGE_HEAD to cause the next git commit command to
create a merge commit. This allows you to create a single commit on top of the current branch whose effect is the
same as merging another branch (or more in case of an octopus).

With --no-squash perform the merge and commit the result. This option can be used to override --squash.
...

んー、なんとなくわかるような気もするけど、イマイチよくわからんので試してみた。
英語がダメなだけとも言う。

試してみた

「2」というコミットメッセージのログを持つmasterからb1ブランチを作ります。
fast-forward*1な状態でb1からmergeしたいので、適当に1ファイルaddして、「4」とメッセージを入れます。
コミットメッセージの数字が連番じゃないのは他の動作検証もしてたので…メッセージ簡略化しすぎてむしろわりづらかったかも。
(ログはユーザ名削除とかで整形してるので実際の表示とは異なります)

マージ前

masterブランチ

* 2f1eea2 2011-03-10 | 2 (HEAD, master) 

b1ブランチ

* 28031c0 2011-03-10 | 4 (HEAD, b1)
git merge b1でマージ (--ff動作)

ログ

* 28031c0 2011-03-10 | 4 (HEAD, master, b1)
* 2f1eea2 2011-03-10 | 2
git merge --no-ff b1でマージ
*   54627d3 2011-03-10 | Merge branch 'b1' (HEAD, master) 
|ヽ 
| * 28031c0 2011-03-10 | 4 (b1)
|/  
* 2f1eea2 2011-03-10 | 2 
git merge --squash b1でマージ
* 2f1eea2 2011-03-10 | 2 (HEAD, master) 

あれ?ログがマージ前と変わらない。
git statusしてみると…Changes to be committed:にコミットメッージ「4」で修正したファイルがあった。
ということで、--squashはマージした結果、コンフリクトが無くてもワークツリーに展開して、その後自分でコミットする模様。
ちなみに複雑になるので上でパターンには入れてないけど、以下動作も留意の事。

  • コンフリクトがあるときはオプションに関わらず自動的にコミットされない。(--squash相当の動作)
  • --ffでもfast-forwardじゃないときはマージコミットができる。
  • --no-ffでマージコミットを作っておけば「git revert HEAD」や「git reset HEAD^」でマージを無かったことにするのは簡単。
まとめ

ということで…

  • ちょっとした取り込みにはオプション無し(--ff)
  • 戻す恐れがあるマージでマージコミットを必ず作りたいときは--no-ff
  • 改変して取り込む必要があるときに--squash

というように使い分けると良い。と理解。違ったら指摘ください。

gitは機能豊富で概念も複雑で嫌気がさす事もあるけど、理解するとホントに便利ですよ!
まだまだたくさん知らない機能あると思いますけどね…

*1:マージ対象ブランチが自ブランチのコミットを全て持っている=マージせず単純に取りこむのみ