git pull と git pull --rebase の違いって?図を交えて説明します! ## はじめに こんにちは、クレイの亀井です。ここ最近一気に気温が上がりましたね。顔に重点的に汗をかくタイプの私には憂鬱な季節がやってまいりました😩💦 さて、今月正式リリースしました(!) DocBase プロジェクトではクレイ外部のデザイナーの方と一緒に開発しています。SourceTree で Git を使っている方で、軽いデザイン修正などは弊社の Rails プロジェクトに直接手を加えてプルリクエストを送ってくれます。 こちらのデザイナーさんに「プルリクエストを送る際は、作業ブランチで **git pull --rebase origin master** してから送ってもらえますか?」とお願いすると**「pull はわかるんですけど、この --rebase ってなんですか?これつけると何が変わるんですか?」**と質問がきたのです。 作業ブランチで **git pull --rebase origin master** してほしい理由はなるべく最新の **master** に近い状態でプルリクエストを送ってほしいからなのですが、デザイナーさんは作業ブランチに最新の **master** を取り込むのに **git pull origin master** を使っていました。 なぜ **git pull --rebase** する必要があるのか、改めて説明しようとするととても難しく、口頭では説明しきれなくて自分自身の力不足を感じました。。 ## もっと段階を踏んで理解する必要がある 業務の中で Git のことが理解できるようになってからわかったことですが、**git pull --rebase** を理解するには、なんと**fetch**、**merge**、**rebase** の3つの Git コマンドをちゃんと理解する必要があります。ヒャー! この記事では図を交えながら、感覚的に理解できることを目指して説明していきたいと思います。 長くなりますが、ファイトです! [Gitに慣れていない方はこちらをどうぞ。](https://kray.jp/blog/git-why-explanation/) さっそく説明といきたいところですが!今後の説明に使うので、まずはローカルリポジトリとリモートリポジトリについてさらっとおさらいします。 ## ローカルリポジトリとリモートリポジトリ Git にはローカルリポジトリとリモートリポジトリという2種類のリポジトリがあります。ローカルリポジトリは作業者のPCにあるリポジトリ、リモートリポジトリは共用のサーバに設置されているリポジトリです。 ローカルリポジトリで作業して、適宜リモートリポジトリにプッシュするというのがGitを使った開発の大まかな流れになります。 Git のローカルリポジトリには * ワーキングツリー * インデックス * ローカルブランチ * リモート追跡ブランチ があります。 ![image.png](https://image.docbase.io/uploads/b19d075e-f4eb-4c5f-98df-f0feb8294d6a.png =WxH) ローカルリポジトリの中に「リモート追跡ブランチ」というのがいますが、これはリモートリポジトリのコピーです。これだけ言われてもなんのこっちゃと思う方もいらっしゃるかもしれませんが、これからの説明の肝になるので注意しておいてください! ではではざっくり Git の構造を図にしたところで、**fetch**、**merge**、**rebase**、**pull & pull --rebase** の説明をしていきたいと思います。 ## git fetch とは **git fetch** とは「リモートのコピーをローカルにダウンロードする」コマンドです。ローカルのリモート追跡ブランチ(リモートのコピー置き場)にコピーしてくるだけなのでワーキングツリーには何も影響を及ぼしません。 ``` git fetch ``` 上記のコマンドを実行した場合の動きを、図に表してみます。 ![image.png](https://image.docbase.io/uploads/f77df1ee-ea21-49a1-a26a-5819c914ce6a.png =WxH) ![image.png](https://image.docbase.io/uploads/1741166d-4c8e-4fd9-820e-5175dc09add1.png =WxH) 1. リモートのコピーちょうだいって言う 1. ローカルとの差分を見つつ、リモートリポジトリが変更や新規ブランチをリモート追跡ブランチに渡す 1. リモート追跡ブランチが更新される ちなみにこの「リモート追跡ブランチ」とはあくまでリモートの状態を示すもので、ユーザー自身がこのブランチの内容を変更することはできません。(内容を見たりとかはできます)また、リモートの状態は自動的に反映されるわけではなく、git fetchコマンドで明示的に行う必要があります。※[SourceTree](https://www.atlassian.com/ja/software/sourcetree/overview)には自動フェッチ機能があるそうです。(使ったことないのであまりよくは知らないです、すみません) このリモート追跡ブランチの内容を見たり、ローカルブランチにマージしたりするときは ``` git merge origin/master ``` というふうに、**origin/ブランチ名** の形でアクセスできます。 ## git merge とは **git merge** とは、今いるブランチに別のブランチの内容を結合させるコマンドです。リモートリポジトリにあるブランチや、ローカルの別のブランチをマージしたいときに使います。 ローカルブランチがこんなかんじであるとしまして(**master** や **hige** はブランチ名です) ![image.png](https://image.docbase.io/uploads/cc7b1cd3-7f80-4558-ba58-fcdf425eec83.png =WxH) **hige** ブランチに **master** ブランチの内容をマージしたいときは、以下のコマンドを使います。 ``` git checkout hige // 現在higeブランチにいるなら不要 git merge master ``` ![image.png](https://image.docbase.io/uploads/9d1530e1-6d0b-4808-ab75-4cfe5e3eea65.png =WxH) これで **hige** ブランチの中に **master** ブランチの内容が取り込まれます。また、マージした場合は「マージコミット」というコミットが積まれます。 また、マージには **Fast-Forword** と **Non-Fast-Forword** という2種類のマージがありますが、ここでは割愛します。わかりやすく解説してくださっている方がいるので、そちらを参照してください。 【git】分かりやすく!mergeは「合流」、rebaseは「付け替え」! ### リモートリポジトリのブランチをマージする場合 ちなみに、上記のコマンドはローカルリポジトリの **master** ブランチをマージするものなので、リモートの **master** ブランチをマージするには以下のコマンドを実行します。 ``` git fetch // 先にリモートの状態をリモート追跡ブランチにコピー git merge origin/master // リモートのコピーからマージ ``` まず **git fetch** でリモートにリモート追跡ブランチを合わせたあと、 ![image.png](https://image.docbase.io/uploads/ab0b8774-f80d-4fa5-9b08-c459c1ad36f0.png =WxH) **git merge origin/master** で **master** に **origin/master** の差分が取り込まれます。 ![image.png](https://image.docbase.io/uploads/e81575a8-4823-49db-a6f1-f6fce6fabccd.png =WxH) **origin/ブランチ名** はリモート追跡ブランチのことでしたね。(**fetch** のセクション参照)**git merge** コマンドでリモートのブランチをマージしたい場合は、いったんリモートのコピーをとってきて、そのコピーをマージするという手順になります。 ## git rebase とは **git rebase** とは、**merge** と同じく今いるブランチに別のブランチの内容を取り込むコマンドですが、**merge** とは動きが違います。 前提は、**git merge** の時と同じこちらの状態です。 ![image.png](https://image.docbase.io/uploads/cc7b1cd3-7f80-4558-ba58-fcdf425eec83.png =WxH) この状態で、**hige** ブランチを **master** にリベースしたい場合、以下のようになります。 ``` git checkout hige // higeブランチにいるなら不要 git rebase master ``` まず、**hige** ブランチにあるふたつのコミットを一時的に保存、**hige** ブランチをリベース先のブランチに **git reset --hard** します。 ![image.png](https://image.docbase.io/uploads/8c5c2d11-73f7-47bb-ab37-5b1c127aaaba.png =WxH) リセット後の **hige** ブランチの上に一時保存していたコミットを乗せます。 ![image.png](https://image.docbase.io/uploads/a5d9030c-b0a1-459e-951d-508e1f2269c6.png =WxH) **hige** ブランチの猫には口がなかったのに、リベース後は口がついてますね。差分がコンフリクトしていなければ Git がうまいことくっつけてくれます。(コンフリクトしたときはエディタを開いて手動で直しましょう。。。) ただ、マージと違ってリベースの場合はコミットを新しく作りなおすので、リベース前とはコミット自身の id と親の id が変わります。**master** の変更を取り込んで別物になったので当然ですね。 ![image.png](https://image.docbase.io/uploads/c0035cd2-3fff-4fdb-83de-fc5fb62abef2.png =WxH) 親のidが変わると、すでにリモートリポジトリにブランチをプッシュしていた場合、リモートにあるブランチの親の id が一致せずプッシュできなくなり(rejectされ)ます。(**git push -f** で強制的にプッシュできますが、よくわかっていないうちは使わないほうがいいでしょう。) マージは現在のブランチの上に他のブランチの内容を取り込むというかんじですが、リベースは取り込みたいブランチの上に今のブランチの内容を乗せるといったかんじですね。 ## git pull とは **git pull** とは、さきほど説明した**git fetch** と **git merge** をいっぺんに実行するコマンドのことです。 **git merge** のセクションで言っていた以下のコマンド。 ``` git fetch // 先にリモートの状態をリモート追跡ブランチにコピー git merge origin/master // リモートのコピーからマージ ``` こちらのコマンドは、 ``` git pull origin master ``` とイコールです。フェッチしてからマージするのMENDOIと思うようになったら使いどきです。 ## git pull --rebase とは ついにお待ちかねの **git pull --rebase** です。 **--rebase** は **git pull** コマンドのオプションです。**git pull** は **fetch** + **merge** ですが、**--rebase** オプションをつけると **fetch** + **rebase**として実行します。 ``` git pull --rebase origin master ``` ## git pull と git pull --rebase の違い 冒頭で以下の様なやりとりをデザイナーさんとしていますが、 > 「ブランチを git pull --rebase origin master してからプルリクエストを送ってもらえますか?」 なぜこのようなお願いをしたかといいますと、**--rebase** オプションをつけてプルしたほうが**マージコミットが作られない&履歴が綺麗になる**からです。 ![image.png](https://image.docbase.io/uploads/f904776b-ab8e-4c3f-a271-f43f4591d65d.png =WxH) このときに **--rebase** オプションをつけなくても「作業ブランチに最新の **master** を取り込む」という目的は達成できるのですが、上記の理由があるため作業ブランチに **master** を取り込むときは **git pull --rebase** でお願いしていました。 ただこちらはいろいろ議論がありまして、(「ログが綺麗になるとしても履歴を書き換えるべきではない」など)いきなりリベースをするとチームの規約に反する場合もありますので、そのあたりは事前にチームの人に確認してください。 また、マージするのに比べてリベースするほうが差分がコンフリクトしたときに修正するのが面倒なときがあったりするので、あまり Git に慣れていないうちは **git merge** でリモートリポジトリの差分を取り込むほうがいいかもしれません。 ## 最後に **git pull** と **git pull --rebase** の違いを説明するだけなのに、なんだかとても長くなってしまいました。そして、書くのに時間がかかりすぎて冒頭で質問してくれたデザイナーさんはすでに DocBase チームから離れていました。。。Kさん、見ていらっしゃいますでしょうか…? Git はコミットさえしていれば間違えてコミットやブランチを消してしまっても元に戻せる方法があるので、テスト用のブランチを切って試してみるのもいいかもしれません💪元に戻しやすいのは Git のいいところのひとつですね! ここまで読んでくださった方、ありがとうございました!この記事を読んで Git の理解が少しでも進めば幸いです。