gitはfetch, add, commit, pushだけ知ってれば大丈夫
これは私が新卒時代に「Git は何を勉強すればいいですか?」と先輩に聞いたときの言葉です。
実際、プロジェクトのブランチ運用ルールが確立され、適切に守られているのであれば、この言葉の通りかもしれません。
しかし、ブランチルールから逸脱した操作 が発生すると、コミット履歴を追跡し、原因を特定して対応しなければならなくなります。そのためには、Git の仕組みを理解しておく必要があります。
この記事では、「Git のブランチの正体」と「2つのマージ方法」 について解説し、私自身の理解を深めるためにまとめました。
ブランチの正体
git commit を実行すると、Git は「コミットオブジェクト」と呼ばれるデータを作成します。
このコミットオブジェクトには、以下の情報が含まれています。
- コミットの内容(変更履歴)
- 親コミット(修正)へのポインタ
- コミットメッセージ
ブランチの正体は、最新のコミットを指すポインタです。
例えば、以下のようなコミット履歴があるとします:
A - B - C (main)
ここで main
ブランチは Cのコミットを指しているポインタ です。
そして、今どのブランチを見ているかを指すポインタがHEAD
と呼ばれるものです。例えばmain
ブランチからfeature
ブランチを作成し、feature
ブランチに切り替えたとします。そうするとHEAD
はmain
からfeature
に移動します。
A - B - C (main)
\
(feature, HEAD)
以降の説明では HEAD は省略しますが、ブランチを移動すると HEAD も移動している ということを意識してください。
2つのマージ方法
ブランチの仕組みが分かったところで、マージ方法について説明します。Gitのマージには、以下の2種類があります。
- Fast-forward マージ
- Three-way マージ
この違いを説明するために、以下のブランチを使います。
- main ブランチ(基準となるブランチ)
- feature/1 ブランチ(main から分岐)
- feature/2 ブランチ(main から分岐)
Gitのブランチは最新のコミットを指すポインタなので、作成直後の3つのブランチは同じコミットを指している状態です。以下の図では、各ブランチが指すコミットを箱(コミットハッシュ)で表しています。
以降の説明はこの状態からスタートします。
fast-forward
今mainブランチにいるとして、feature/1ブランチに移ります。そしてそこでtext_1.txtを追加するという変更を行い、それをcommitします。するとコミット履歴とブランチの状態は以下のようになります。
この状態でmain
にfeature/1
をマージすると、Gitは次の3つの情報を確認します。
対象 | ブランチ名 | コミットハッシュ |
---|---|---|
マージ元 | feature/1 | 032fe65 |
マージ先 | main | 7e2afb5 |
マージ元/先の共通先祖 | main | 7e2afb5 |
この状態をみると、マージ先であるmain
の最新コミットがfeature/1
との共通の祖先のコミットと一致しているため、単純にポインタをfeature/1
に進めるだけでOKです。
マージ元/先の共通の先祖はgitが自動的に判別してくれています
% git merge feature/1
Updating 7c2afb5..032fe65
Fast-forward
text_1.txt | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
このように、ポインタを進めるだけのマージをFast-forwardマージと呼びます。
three-way-merge
次にfeature/2
ブランチに移り、text_2.txtというファイルを追加する変更を行います。するとコミット履歴とブランチの状態は以下のようになります。
この状態でfeature/2をmainブランチにマージします。今回のケースではマージ元/先、共通の先祖は以下のようになっています。
ブランチ種別 | ブランチ名 | コミットハッシュ |
---|---|---|
マージ元 | feature/2 | 9512fe5 |
マージ先 | main | 032fe65 |
マージ元/先の共通先祖 | main | 7e2afb5 |
先ほどのfast-forwardと違ってすべてコミットハッシュがバラバラになっています。この状態でmain
ブランチに移りfeature/2
をマージするとどうなるでしょう?
% git merge feature/2
Merge made by the 'ort' strategy.
text_2.txt | 1 +
1 file changed, 1 insertion(+)
create mode 100644 text_2.txt
このように無事マージはできましたが、fast-forwardの文字が表示されていません。これは、今回のようなパターンではthree-way-mergeと呼ばれる方法でマージが行われたからです。
Gitは共通の祖先7e2afb5
を基にmain
の032fe65
とfeature/2
の9512fe5
を統合し、新しいマージコミットa899dae
を作成します。
「共通の祖先」をもとに 3 つのコミットを比較して統合するのがThree-way Mergeと呼ばれるマージ方法になります。おそらく案件などでよく見かけるのはこちらのパターンになると思います。
まとめ
gitブランチの正体とマージのパターンについてまとめました。時々必要に駆られてrevert
やreset
の操作をするのですが、仕組みがよくわからかったのがこのgitの基本を知ってからようやく意味がわかるようになりました。gitの公式ドキュメントがかなり優しく詳しい解説をしているので一度読んでみるのをおすすめします。
参考記事