git merge オプションの --ff, --no-ff, --ff-only の違い


Git コマンド git merge で使われる次の 3 つのオプション --ff, --no-ff, --ff-only の違いを図付きで, できるだけわかりやすく解説します.

git merge --ff の場合

git merge --ff でブランチ同士をマージさせると, そのマージがファストフォワードとなる場合, マージコミットを作らず, HEAD だけ移動させます.

このオプションは git merge コマンドのデフォルトになります.

次のようなヒストリで:

                         A --- B --- C <- feature
                        /
                       /
    D --- E --- F --- G <- master <- HEAD

次のコマンドを実行すると:

git merge --ff feature

このようなメッセージが表示され:

Updating c7897ae..1115653
Fast-forward
 example.txt | 3 +++
 1 file changed, 3 insertions(+)

次のようなヒストリになります:

    D --- E --- F --- G --- A --- B --- C (HEAD -> master, feature)

この --ff オプションは git merge でマージする時のデフォルトなので, merge.ff の設定をいじられていない場合, git config --global merge.ff true などと設定する必要はありません.

git merge --no-ff の場合

git merge --no-ff でブランチ同士をマージさせると, そのマージがファストフォワードとなる場合であっても, マージコミットを作ります.

次のようなコミットで:

                         A --- B --- C <- feature
                        /
                       /
    D --- E --- F --- G <- master <- HEAD

次のコマンドを実行すると:

git merge --no-ff feature

次のようなコミット作成画面が表示され:

Merge branch 'feature'

# Please enter a commit message to explain why this merge is necessary,
# especially if it merges an updated upstream into a topic branch.
#
# Lines starting with '#' will be ignored, and an empty message aborts
# the commit.

それをコミットすると, 次のようなメッセージが表示され:

Merge made by the 'recursive' strategy.
 example.txt | 3 +++
 1 file changed, 3 insertions(+)

次のようなヒストリになります:

                         A --- B --- C <- feature
                        /             \
                       /               \
    D --- E --- F --- G --------------- H <- master <- HEAD

また --no-ff オプションは refs/tags/ 階層の通常の場所に保存されていない注釈付きタグや署名付きタグをマージする時のデフォルトになるようです.

もしこの --no-ff オプションを git merge でマージするときのデフォルトと設定する場合, 一例として git config --global merge.ff false で設定できます.

git merge --ff-only の場合

git merge --ff-only の場合, 現在の HEAD が最新状態もしくはマージがファストフォワードでない場合, そのマージは拒否され, ノンゼロステータスで終了します.

次のようなヒストリで:

             A --- B --- C <- feature
            /
           /
    D --- E --- F --- G <- master <- HEAD

次のコマンドを実行すると:

git merge --ff-only feature

次のようなメッセージが表示され:

fatal: Not possible to fast-forward, aborting.

そのマージはファストフォワードとならないので, 中止されます.

なのでヒストリは変わらず元のままです:

             A --- B --- C <- feature
            /
           /
    D --- E --- F --- G <- master <- HEAD

もしこの --ff-only オプションを git merge でマージするときのデフォルトと設定する場合, 一例として git config --global merge.ff only で設定できます.

まとめ

git merge--ff オプションは, git merge コマンドのデフォルトなので, merge.ff の値を true 以外に設定していない限り, 付けても意味はありません.

--no-ff オプションは, 本来ファストフォワードとなるマージを, そのようにせず代わりにマージコミットを作ります. マージされるブランチとマージするブランチの本来のヒストリを残せるので, どのようにコミットが作られてきたかという経緯をそのまま残したい場合に使われます.

--ff-only オプションは, 現在の HEAD が最新状態もしくはマージがファストフォワードでない場合, マージを拒否するので, マージコミットを作るようなマージを防ぐことができます. ファストフォワードマージのみ行いたいという場合に使われます.

ファストフォワードマージはそのままにするのか, それともマージコミットを作るのか, もしくはファストフォワードとならないマージは中止させるのかというのはそれぞれの状況によるかと思います.

それら 3 つのオプションの違いを表で表すと次のようになります:

マージが FF の場合 FF でない場合
--ff そのまま そのまま
--no-ff FF にしない そのまま
--ff-only そのまま 拒否する

それぞれの状況に合わせて, より良いと思われる方法を選択していただければと思います.

参考資料