Git リベースの動きと注意点


Git の rebase の動きを図で説明しつつ、注意点を解説します。

まずは基本的な動きからです。○がコミットを表します。○の中身はコミットメッセージだと思ってください1通常はもっとわかりやすい文章をコミットメッセージとしましょう

rebase という名の通り、ブランチのベース(付け根部分)の位置を移動します。画像は feature ブランチを master ブランチに rebase する例です。

まずは feature ブランチの付け根の位置について見てみます。master と feature ブランチの「共通の祖先」から分岐する部分が付け根です。例では「ろ」から「に」の部分ですね。

feature の付け根から先のコミットを master ブランチの位置にあるコミット「は」から続くように移動しています。これが rebase の基本の動きです。

rebase の動きを正確に知る

付け根の位置を移動すると書きましたが、実は正確な表現ではありません。実際は以下のような動きをしています。

ベースになるコミットの場所を変えて、コミットをやり直すイメージ

feature の付け根から先のコミットを、master の位置からコミットし直しています。その後、feature ブランチの位置を新しい「へ」のコミットに移動しています。

コミットを「移動」するというより、コミットを「コピー」してブランチを移動する、というのがより正確な表現です。

ブランチがついていない枝は見えなくなるので、旧「に」「ほ」「へ」コミットは削除されたように見えます。実際は reflog を使用すれば消えたコミットも見ることができます。通常はブランチが紐づかないコミットは見えないようになっている、というだけですので、コミットが完全に消えたわけではないです。

rebase 時に競合が発生した場合、この動きが頭に入っていれば混乱しなくて済むはずです。

対話モード

rebase する際に、対話モードというオプションを指定することができます。コミットをまとめたり編集したりしながら rebase できる機能です。

ここではコミットをまとめる際の挙動について解説します。

例は master に rebase しているが、コミットをまとめるだけなら「ろ」に rebase もできる

インタラクティブリベースを実行すると、どのコミットをどのようにリベースするか入力を求められます(テキストエディタが起動します)。そこで、pick, fixup, squash の3つを選ぶことができます。

pick は通常の rebase と同じように、コミットをコピーします。

fixup は1つ前のコミットにソースをマージします。コミットを1つにまとめたい際に使用します。

squash は fixup とほぼ同じなのですが、fixup はコミットのメッセージを破棄するのに対し、 squash はコミットメッセージを前のコミットに追記します。

rebase で困る例1

rebase をするとコミットログを綺麗に保てるというメリットがありますが、もちろんデメリットも存在します。rebase の問題、というか push –force の問題なのですが・・・。

A さんと B さんという人物が登場します。二人とも feature ブランチをチェックアウトしているとします。

feature ブランチはすでに origin へ push されています。そのブランチを A さんが rebase し、強制プッシュしてしまいました。

すると、 feature ブランチに入っていた B さんは、ローカルの feature ブランチと origin の feature ブランチの付け根が変わってしまい、非常に混乱します。

今回は、 A さんも B さんも修正をしていないので、 reset するか、 B さんもリベースすることで平和的に解決できます。ただ、A さん B さんがそれぞれコミットしていたり、A さんが rebase 時に fixup してしまったりすると面倒なことになります。あまり想像したくないです。

そもそもブランチは個人専用にすべきという話もありますが、Aさんのブランチのソースに B さんが書いたソースが依存していたりするとそうも言っていられません。

このような問題が考えられるので、 push した branch は rebase しないようにしましょう。100%他人に影響ないということが言えるなら別ですが(一人で開発しているなど)。

rebase の副作用

rebase はコミットを移動ではなくコピーしている、と書きましたので、もしかしたらお気づきかもしれませんが、rebase をするとコミットのハッシュ値が変わります。コミットメッセージなどの内容は維持されますが、コミットの親コミットが変わりハッシュ値の生成元である「コミットオブジェクト」の内容が書き変わるためです。

同じコミットに見えてもハッシュ値は違う

コミットハッシュが変わると困る場合があるので、以下で説明します。

rebase で困る例2

今回は A さん B さん C さんが登場人物です。

B さんと C さんが会話していて、Bさんが参考になるソースを C さんに教えようとしています。

ほぼ同じタイミングで、運悪く A さんがリベースを実行し、 B さんが教えたコミットが消えてしまいました。

コミットが消える

このように、コミットハッシュが変わることでコミットが行方不明になることがあります。他にも、

  • バグが起きたコミットのハッシュ値を記録して、バグ発生の分析をしている
  • 参考用の差分としてコミットハッシュをドキュメントに記載している

といった運用をしていると問題が発生することが考えられます。予防策として、特定のコミットを保持したい場合はブランチかタグをつけておくと良いです。

まとめ

rebase は便利ですが、push したブランチに対して行う場合は充分注意する必要があります。絶対 rebase は使わない方がいい、ということはないので、ローカルであればどんどん rebase して綺麗なコミットログを作りましょう。


コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です

このサイトはスパムを低減するために Akismet を使っています。コメントデータの処理方法の詳細はこちらをご覧ください