掲載内容は個人の見解であり、所属する企業を代表するものではありません.
恥ずかしながら未だに Git のマージの挙動に慣れていないのですが、とりあえず以下を参考に挙動を確認してみた、という記事です。
なおここでは話を簡単にするためにコンフリクトは発生しないこととしています。 またブランチ戦略等のお作法も一旦忘れます。
いろいろなオプションを指定したマージの挙動を確認したいので、まずは同じ初期状態を再現するためのコマンドを用意してみました。 master ブランチと topic ブランチに分岐してそれぞれ2つ Commit が進んだ状態になります。
mkdir temp
cd temp
git init
echo A > A.txt
git add .
git commit -m "A"
git branch topic
echo B > B.txt
git add .
git commit -m "B"
echo C > C.txt
git add .
git commit -m "C"
git checkout topic
echo Y > Y.txt
git add .
git commit -m "Y"
echo Z > Z.txt
git add .
git commit -m "Z"
git checkout master
この状態を元にいろいろなマージを試してみるのですが、その前後の状態を確認するために Visual Studio Code の GitLens Extension を使用しています。
まず先ほどの初期化コマンドを流した直後は以下の状態になっていました。
マージ先となる master ブランチ(コミット C)は、マージ元の topic ブランチ(コミット Z)の直接の祖先ではないため Fast-Forward のマージが出来ないはずです。
このため --no-ff
オプションを付けて topic ブランチをマージすると、、、
git checkout master
git merge --no-ff topic
コミット C とコミット Z を親とするマージコミットが作成されます。 Topic ブランチで作成したコミット Y と Z も維持されていることが分かります。
git reset
で以前の状態に戻してもいいのですが、ここでは別の歴史をつくります。
改めて、先ほどの初期化コマンドを流した直後は以下の状態になっていました。
このため各コミットのハッシュは変わっています。
特にオプションを付けずに master ブランチに対して topic ブランチをマージすると、、、
git checkout master
git merge topic
先ほどの --no-ff
オプションを付けたマージと同じく、コミット C とコミット Z を親とするマージコミットが作成され、Topic ブランチで作成したコミット Y と Z も維持されていることが分かります。
git merge
コマンドの リファレンス によると、
既定では --ff
オプションが指定されることになっているのですが、Fast Forward が可能な状態じゃないので、Non Fast Forward と同じ挙動になったということですね。
改めて、先ほどの初期化コマンドを流した直後は以下の状態になっていました。
今度は --squash
オプションを付けて master ブランチに対して topic ブランチをマージしてみましょう。
git checkout master
git merge --squash topic
master ブランチの HEAD (コミット C)を親とする、コミット Y や Z とは紐付かない新しい変更が追加され、未コミット状態になります。
この状態でコミットすると、、、
git commit
コミット C のみを親とするコミットが作成されます。
このケースではコミット Y と Z の 2 つだけですが、作業中のブランチのコミット履歴が多くなりすぎると追いかけるのが大変なので、1つのコミットとして纏められるのは便利そうです。 ただ後から master 側の履歴を見ると( topic ブランチとは紐付いていないので)、イキナリ master ブランチにコミットが追加されたような感じになっていますね。
改めて、先ほどの初期化コマンドを流した直後は以下の状態になっていました。
topic ブランチのベースを master ブランチに合わせると、、、
git checkout topic
git rebase master
コミット Y と Z が C の後ろにぶら下がり、履歴が一本になります。 またコミット Y と Z のハッシュも書き換わっていることが分かります。 先祖が変われば子孫も変わるということでしょうか。
この状態で通常のマージをすると、
git checkout master
git merge topic
master ブランチの HEAD と topic ブランチの HEAD がどちらもコミット Z を指しすようになります。
後から見るとどこで作業が分岐したのかが分からず、マージが行われたのか否かもわからない、といったところでしょうか。
改めて、先ほどの初期化コマンドを流した直後は以下の状態になっていました。
topic ブランチのベースを master ブランチに合わせると、、、
git checkout topic
git rebase master
コミット Y と Z が C の後ろにぶら下がり、履歴が一本になります。
この状態で --no-ff
オプションを付けてマージすると、、、
git checkout master
git merge --no-ff topic
コミット C とコミット Z を親とするマージコミットが作成され、master ブランチの HEAD が進みます。 コミット Y と Z は維持されておりハッシュは書き換わっていますが、 topic は Z を指し示したままになっています。