git pullはリモートの変更をローカルに取り込むコマンドで、gitユーザーなら頻繁に使うコマンドの一つだと思います。
git pullには–rebaseと言うオプションをつけることで、リベースをするpullができるようになります。
この記事では、git pullとgit pull –rebaseの違いについて、図を使って詳しく解説していきます。
git pullとはなにか
まずはgit pullから見ていきましょう。
git pullはリモートの最新の変更をローカルに取り込むコマンドです。
例えば、以下の図のような状況を例にしてみます。
それぞれの丸はコミットを表現しており、青い矢印はブランチを表しています。

図の中ではコミットAが一番古く、B、Cと進むとより新しいコミットになります。
ご覧の通り、mainとfeatureAと言う二つのブランチがあり、現在はfeatureAをチェックアウトして新規機能開発をしているとしましょう。
この状態から、リモートのfeatureAブランチに誰かがコミットEをpushしたとします。

この変更をローカルに取り込むには、git pullを実行します。
git pullは内部的にはgit fetchとgit mergeという二つのコマンドが実行されています。
ここでは理解しやすいように一つづつコマンドを実行してみて、その結果を見ていきましょう。
まずはgit fetchをすることで、リモートのリポジトリの情報をリモート追跡ブランチに取り込むことができます。
※「リモート追跡ブランチ」とは、ローカルにあるリモートの状態を追跡する専用のブランチです。作業で使うローカルブランチとは別で各ブランチごとに用意されます。

図のようにリモート追跡ブランチ(remote/featureA)が更新されました。
次にgit mergeを実行することで、ローカルブランチ(featureA)にリモート追跡ブランチの変更を取り込みます。

これでローカルブランチが更新され、コミットEの内容が実際のファイルにも反映されました。
これがgit pullを実行したときに起きていることです。
mergeコマンドを実行した時のマージの方法にはいくつかの種類がありますが、今回はfast-forwardというマージ方法が行われました。
fast-forwardは、今回のようにローカルブランチにコミットがない場合にリモートの変更をマージすると発生します。
fast-forwardは単純にローカルブランチの先頭にコミットを足せばいいだけという一番シンプルなマージ方法です。
上記の図でも、ローカルブランチ(featureA)をコミットCからEに進めるだけで完了しています。
※fast-forwardは”先に進める”や”早送り”の意味
まずはpullの説明のためにシンプルなfast-forwardマージを紹介しましたが、rebaseを理解するためにもう一つ別のマージ方法を紹介します。
前と似た状況で、リモートに誰かがコミットEをpushしたとしましょう。ただし今回はローカルにも別のコミットFがあります。

このようにリモートとローカルで異なるコミットが存在している場合、マージしようとするとそれらをうまく混ぜ合わせないといけません。
このマージ作業はgitが自動的に行なってくれますが、同じファイルの同じ箇所をお互いに変更していた場合はgitが解決できないので人が対応しなければいけません。
そのような状況をコンフリクト(競合)が発生したと言います。
先ほどと同じく、pullした時の動きをまずはfetchから見ていきましょう。

図のようにリモート追跡ブランチ(remote/featureA)が更新されました。
コミットEとコミットFはどちらもコミットCをベースとしているため、ツリーが分岐しています。
次にgit mergeを実行します。

今回は単純にfast-forwardマージできないので、3-way mergeというマージ方法が使われます。
これは、分岐しているコミットEとコミットFと、それらのベースであるコミットCの3つを比較しながらマージする方法です。
fast-forwardマージと同じく、今回もgitで自動的にマージされますが、同じファイルの同じ行を編集している場合はやはりコンフリクトが起こります。
また、3-way mergeの特徴として、マージが完了すると変更した内容でマージコミットと呼ばれる新しいコミットが作られます。(図のコミットG)
そのため、マージコミットがあるということは、通常の変更ではなく二つの分岐をマージしたということが分かります。
ローカルの変更が済んだので、リモートにpushします。

これでリモートと同期を取ることができました。
git pull –rebaseとは何なのか
それではいよいよrebaseの解説に入っていきます!
git pull –rebaseをすると、git fetchとgit mergeではなく、git fetchとgit rebaseと言うコマンドが実行されます。
rebaseコマンド(git pull –rebaseした場合)は、以下の動作を行います。
- 現在のローカルブランチだけにあるコミットを一度取り外す
- リモートの状態をそのままローカルブランチに取り込む
- 1で取り外したコミットを、ローカルブランチの先頭に適用する
これだけでは何を言っているかわからないと思うので、実際に動きを見ていきましょう。
まずgit fetchした状態はgit pullの時と同じです。

次にgit rebaseを実行しますが、ステップを2つに分けて解説します。
まずrebaseが実行されると、gitはローカルのコミットFを一度ブランチから取り外し、リモート追跡ブランチの状態に更新します。

その後、取り外していたコミットFをローカルブランチの先頭に適用します。

これでgit pull –rebaseが完了です。
rebaseが何をしているかというと、名前の通り、親であるベース(base)のコミットを再(re)設定しています。
図の中では、コミットFの親は元々はコミットCでしたが、rebaseすることで親をコミットEにしています。
結果的にリモートの最新のコミットを親にしたため、そこから進んだコミットは今は自分しかしていないので、単純に先頭に適用すれば良いだけです。
コミットを「適用」すると言うのは、mergeコマンドで行うファイルをマージするのと大体同じですが、マージと言うと混乱するので適用と言っています。
なお、ローカルのコミットが複数ある場合は、古いコミットから一つづつ順番に適用していきます。
git pull –rebaseのメリット
rebaseのメリットは、コミット履歴が一直線になりシンプルになると言うことです。
先ほどのrebaseとmergeの結果を比べてみると、コミット履歴の形が異なることがわかります。

mergeでは分岐したコミットの線がそのまま残ることやマージコミットが作られることからコミットの履歴が複雑になります。
この程度の例ではあまり複雑だと感じませんが、実際の開発では分岐が何十にも重なることもあり、履歴が見にくくなってしまうと言うデメリットがあります。
一方、rebaseではコミット履歴が一直線になり、単にコミットを辿ればその歴史が見れるのでわかりやすい形になっています。
git pull –rebaseのデメリット
git pull –rebaseのデメリットの一つは、コンフリクトが複数回発生することがある点です。
rebaseを実行すると、ローカルブランチのコミットを一つずつ適用していくため、コンフリクトが何度も発生する可能性があります。
mergeであれば一回のコンフリクトで解決できるので、より簡単と言えるでしょう。
別のデメリットとして「そもそもリベースは危険だから使わない方がいい」と言う意見もネットなどで見られます。
ただ、これはrebaseコマンドのことであり、git pull –rebaseでは問題ないと思います。
リベースが危険と言われる所以は、コミットを一度取り外して適用し直すため、コミットIDが変更されてしまうことが原因です。
もし書き換えたコミットがすでにリモートにあり、他の開発者がそれをベースに作業しているとしたら、同期を取るときに整合性が取れなくなり問題が発生します。
そのため、「すでにpushしているコミットのリベースは禁止」「リベースするならローカルでの変更だけにすること」と言うのが定説です。
ただし、git pull –rebaseに関しては、適用し直すコミットは必ずローカルの未pushのコミットに限ります。
git pull自体がリモートの変更をローカルに反映させるだけで、–rebaseオプションはそこに対してローカルの変更をどうマージするのかと言う違いだけです。
リモートにpush済みのコミットを変更するわけではないので、特に問題はありません。
git pull –rebaseを使うべき?
–rebaseオプションを付けるかどうかは、チームの方針や個人の好みなどによって変わります。
大前提として、リモートの変更をローカルに取り込むという目的自体はgit pullで果たすことができます。
–rebaseを付けないと開発が止まったり、何か致命的な問題が起こるということは基本ありません。
その上で、履歴をきれいにしたいという更に別の何かを求めるのであれば–rebaseを付けても良いでしょう。