読者です 読者をやめる 読者になる 読者になる

gitで双方向にmergeしてるとひどいはまり方をするときがある件

gitで双方向にmergeしてるとぎっとぎっとにされる件

gitで,ブランチきって双方向にmergeしたりされたりするときには注意しないと,身に覚えのない衝突しになやまされたりする.自分があまり関知していないコードの衝突を解決する必要がでてきたりして,バグのもとになる.

(23:11まきもどりうんぬんはちょっと違う気がしたので削除)

解決方法としては,

  • masterに自分のブランチをmergeする直前には,かならず自分のブランチにmasterをmergeする
  • 自分のブランチにmasterをmergeしない

のどちらかが妥当そう.

以下つらつらと書いてあるけどスルー推奨.良くわかってないところも多いのでまちがってたら教えてクダサイ.というか,git-mergeのしくみをちゃんと理解できてないので,変なこと行ってる気がしてきた.


追記: gitで双方向mergeしたときに起こった問題を再現 - はこべブログ ♨ に状況の再現と,もう少しましな結論を用意しましたので,そちらもご参照クダサイ.



問題

以下のような状況を仮定する.

  • masterブランチ
    • メインの開発ブランチ
    • いろんな人がコミットする
  • experimentalブランチ
    • 実験的な機能を開発するブランチ
    • ほかの人の開発をトラックするためにちょくちょく merge master する <= BAD

この状況で,experimentalでのmerge masterがmasterの最終コミットよりも前だと,masterでmerge experimentalで,masterがまきもどったり,mergeが衝突しまくったりする.

以下のような操作があたる.

0: $ git checkout experimental
1: $ git merge master # masterをおいかける <= BAD
2: $ # experimentalで開発
3: $
4: $ git checkout master # 別のひとがmasterで開発していて実は 2 よりも新しくなっている
5: $ git merge experimental
6: $ # まきもどったり衝突したりする

原因

experimentalでmerge masterしたあと,masterに更新があったあとで,逆にmasterでmerge experimentalしてる.

理由

(あまりにも,わかりにくいので,とばしてもらったほうが良さそう… あとで図を描く.読む人は,どっちがmergeする側でどっちがmergeされる側か注意して読んでね!)

experimentalでmerge masterを実行すると,masterのコミットがexperimentalにとりこまれる.ここで(masterのコミットがexperimentalと不可分であれば,)あたらしくmergeコミット発生する.このmergeコミットには,この時点でのexperimentalとmaster間の差を埋める変更と,experimentalで行われた変更が混じり合うことになる.

その後,逆に,masterでmerge experimentalを実行すると,experimental側のコミットがmasterにとりこまれる.当然experimentalでmerge masterしたときのmergeコミットもとりこまれる.しかし,このmergeコミットには,masterとexperimental間の意味的に純粋な変更以外にも,mergeコミットが発生した時点でのexperimentalとmasterの差を埋める変更が含まれている.(ややこしい!)

つまり,この時,experimentalでのmerge master時点でのexperimentalからmasterの差分が適応されてしまう.現時点ではその差分が正しくあてられるとは限らず,なぞの巻き戻りや衝突が多発したりする.運良くmergeできても,experimentalをmergeしてるはずなのにmasterで行われた変更が埋め込まれまくった,mergeコミットが発生する.

解決

  • experimental ではなるべく merge masterしない or Fast Fowardなマージ以外しない.
  • experimentalでなるべく merge master する必要がないように,作業を細かく区切り,頻繁にmasterでmerge experimentalする.
  • どうしてもexperimentalでmerge masterする必要があるときは,その後masterでmerge experimentalする直前に,再度experimentalでmerge masterすると,masterが巻き戻ったり,衝突しまくる可能性が低い.

gitでブランチきって作業するときは,

$ git checkout master 
$ git checkout -b experimental #masterからあたらしいブランチをきって作業開始
$ # experimental で 作業
$ # git merge masterはしない
$ git checkout master
$ git merge experimental # masterにmerge
$ git branch -d experimental # mergeがおわったら作ったブランチは消す/リセット (オプション)

というサイクルをなるべく短い間隔でやるのが良さそう.短い間隔の開発ならmerge masterもそんなに必要ないはず.ひとつのタスクを大きくしすぎずに,短い単位でmasterに適応していこう.ブランチ消す/リセットはmasterにmergeさえすれば,そこでツリーが同期されるのでその後の開発に支障はないはずなのだけど,確実.

ちなみに,mergeじゃなくてrebaseすればすべて解決する気もするけど,remoteに複数人がpushしてるときなどにはまるので,この方法も使えない.

このあたりは,もそっと調べる.