git merge に関するメモ

前回、git の運用指針 - Life like a clown と言う記事を書きましたが、git の事をよく理解しない状態のまま取り急ぎ作成したために、やはりいろいろとまずい所があったようです*1。指摘された点としては、「merge や rebase の理解がまずい」、「git-flow はしんどいから GitHub Flow の方が良い」等がありました。これらに関しては一つずつ調べながら、自分が理解した範囲で一度まとめてみようと思います。取りあえず、今回は git merge から。

尚、merge や rebase について調べていると こわくない Git と言う非常に分かりやすくて良い資料(スライド)が見つかりました。git 上で何らかのブランチ戦略を取って開発を行う場合、ひとまずこの資料に目を通す(通させる)と良いかもしれません。

git merge (Non Fast-Forward) 概要

git merege の概要に関しては Git - ブランチとマージの基本 に詳細な説明がありました。尚、git では下記の iss53 のような「何らかの機能追加や修正のために一時的に作成されたリポジトリ」をトピックブランチと呼ぶそうです。また、下記の図のように git では一連のコミットの履歴を有向グラフで表現する事が多く、これをコミットグラフ入門 Git ではコミット家系図 (commit ancestry graph) と表記)などと呼んだりもするようです。

マージ先のコミットがマージ元のコミットの直系の先祖ではないため、Git 側でちょっとした処理が必要だったのです。ここでは、各ブランチが指すふたつのスナップショットとそれらの共通の先祖との間で三方向のマージを行いました。図 3-16 に、今回のマージで使用した三つのスナップショットを示します。

単にブランチのポインタを先に進めるのではなく、Git はこの三方向のマージ結果から新たなスナップショットを作成し、それを指す新しいコミットを自動作成します (図 3-17 を参照ください)。これはマージコミットと呼ばれ、複数の親を持つ特別なコミットとなります。

マージの基点として使用する共通の先祖を Git が自動的に判別するというのが特筆すべき点です。CVSSubversion (バージョン 1.5 より前のもの) は、マージの基点となるポイントを自分で見つける必要があります。これにより、他のシステムに比べて Git のマージが非常に簡単なものとなっているのです。

これで、今までの作業がマージできました。

Git - ブランチとマージの基本

これらの記述を読んで、最初に思った事は「自分が思っているよりもはるかに賢いんだな」と言う事でした。前回の記事で「トピックブランチの修正中にメインブランチ (develop) に何らかの修正が加えられた場合、それを直ちにトピックブランチにも反映する」と言う指針を立てたのは、できるだけ早い段階で修正を反映しておかなければ(トピックブランチからメインブランチへの)マージ時に何か変な事*2になるのではないか、と言う不安からでした。「マージがなんとなく怖い」と言う こわくない Git で紹介されていた git 初心者の心理そのまま、と言う感じです(苦笑)。

しかし、上記の説明やその他の記事を読んでいるうちに、(同じファイルが修正された等の)コンフリクトが発生しそうな場合や修正箇所が該当のトピックブランチの挙動にも関わってくる(トピックブランチでの開発を進める上でメインブランチの修正が必要)場合を除けば、該当のトピックブランチで修正している間は、メインブランチの変更は無視しても構わないのだなと言う気になりました。

マージは常に Non Fast-Forward でと言う議論

git に関する資料を読んでいて、気になっていた事の一つに「マージは常に Non Fast-Forward で行う (git merge -no-ff <branch>)」と言う規則を設けている事が多い、と言うものがありました。

こわくない Git では、Fast-Forward によるマージの弊害として、以下の 2 点が挙げられていました*3

  1. 「ブランチをマージした」という事実が歴史(コミットグラフ)に残らない
  2. 「ブランチのマージ」を取り消しづらい

その他、git log --first-parent と言うコマンドを用いて「修正履歴を見やすくする」と言う事を考えた場合にも Non Fast-Forward によるマージは必要と言う意見がありましたが、いずれに関しても、ある機能に関わる修正(コミット)がどこからどこまでなのかを分かりやすくする、と言う事が、実際の開発・運用に関しては重要になってくる場面も多いため、そう言った戦略が取られるようです。

これは個人的な話ですが、git に関する各種解説を読んでいるうちに「git においては、できるだけコミットグラフを一直線に保つことが良いとされているんだ。だから、可能であれば各種コマンド (Fast-Forward の merge や rebase)を用いて早い段階でコミットグラフを一直線にすべきだ」と言う気になっていました。

しかし、git (と言うか VCS)の目的はあくまで「多人数による開発のための管理や不意の事態(機能の逆戻り等)への対応」をしやすくする事にあるので、そう言った事を考慮せずに「コミットグラフに一直線に保つかどうか」に固執すると言うのは本質からずれる、と言う感じもします。なかなか難しい問題ですが、「実際の開発・運用においては何がベストか」をよく検討して指針を立てられるようにしていきたいと思います。

git merge に関する指針(未完成)

  • トピックブランチで修正中は、基本的には、メインブランチへの修正は無視(保留)して良い。
  • トピックブランチからメインブランチへのマージは、基本的には、Non Fast-Forward で行う(修正量的に軽度のバグフィックスを行う場合は、Fast-Forward でも良い?ような気もするが、要検討)。
  • メインブランチの修正がトピックブランチでも必要になった場合は、どうやって取り込む?普通に git merge <main-branch> とか?

*1:特に、後でいろいろ資料を読んでいるうちに rebase 辺りの部分は危険そうな予感がしたので、取り消して保留としています

*2:例えば、メインブランチ側で修正したものをトピックブランチで再修正=削除したと見なされて、修正をなかった事にされるのではないか、とか。

*3:尚、Fast-Forward によるマージの利点に関しては、rebaseはコミットグラフを綺麗にするためじゃなくてffマージをするためにあるのであった · GitHub「こわくない Git」というスライドを発表しました - kotas.tech 等で言及がありました。