13
10

More than 1 year has passed since last update.

ユースケースで理解するgit rebase コマンドの基本

Last updated at Posted at 2021-09-19

はじめに

エンジニアになって1年ちょっと。流石にGitにも慣れてきた!と思ってました。
つい2週間前までは・・・

これまでコミットは少ないよりも多い方がリバートしやすくて良い、と思っていたのですが
どれがあなたが対応した内容なのかわからない細かすぎて見づらい、と言われました。 :cry:

わかりづらいので、rebaseしてからプッシュしてくださいと言われたのですが
なんだそれ、の状態・・・

rebaseする際に使うコマンドは調べればたくさんヒットするのですが、
どういうコマンドで、なぜこれを使って、使うとどうなるのかがいまいち理解できませんでした。
rebaseは今までコミットしてきた内容を改変することができ、初心者が使うにはハードルが高いコマンドの印象だったのでよくわからないのに使いたくありませんでした。

そんなとき、最近とある本を読んでやっと理解した気になったので、それについて書きます。

説明しないこと

この記事では、rebaseコマンドのユースケースについて説明することが目的のため、
コマンドのオプションなどはあまり説明致しません・・・ :bow:

rebaseとは

英単語の意味を覚えてから技術的な意味を理解するのが、個人的な用語の覚え方です。
こちらによると、rebaseは動詞で3つの意味があるそうです。

  1. To replace the base of a denture.(入れ歯を取り替えること)
  2. To modify core data from which other data is derived in such a way that the final meaning is unchanged.(最終的な意味が変わらないように、他のデータが派生するコアデータを変更すること)
  3. To change the base address of.(ベースアドレスを変更します。)

1でないのは明らかですね。:sweat: おそらく2ですかね。

大体理解できたところで、git rebaseコマンドを打つときの意味を調べてみると

ブランチの親を変える

ということでした。(書籍「独習Git」より)

これだけではよくわからないので、実際にユースケースを見ながら使い方を理解したいと思います。

ユースケースは2つ

rebaseコマンドを使いたい場面というのは、主に2ケースです。

先ほどrebaseに関する記事はたくさんあると書いたのですが、ユースケースが2つあることを書いている記事が日本語では多くない気がしました・・・

1. 上流に追いつく

英語の意味や「ブランチの親を変える」という意味に沿っているのは、こちらのユースケースですね。

上流の変化に対応する

ために使用する場合。(書籍「独習Git」より)

では具体的な例を考えてみます。

あなたは複数人の開発者が関わる開発に関わっています。
ブランチの運用は以下のようになっています。

ブランチ名        用途
main developブランチからマージされる。基本直接プッシュはしない。
develop feature/<タスク名>ブランチからマージされる。基本直接プッシュはしない。
feature/<タスク名> developブランチから切って作成されるブランチ。作業は基本的にこのブランチで行う。

よくある運用だと思います。
実際はもっと細かくあると思いますが、本題ではないので詳しくは踏み込みません。

では以下のような場面を考えてみます。

① あなたは、developブランチから作成した、feature/<タスク1>ブランチで作業をしています。
また、開発者Bさんもdevelopブランチから作成した、feature/<タスク2>ブランチで作業をしています。

rebase_explanation_1.png

② Bさんはタスク2の実装が完了したので、developブランチへのプルリクエストを出しました。
見事レビューが通りdevelopブランチにマージされました。

rebase_explanation_2.png

③ 幸運なことに、BさんはAさんの担当でも実装予定だった共通処理を実装してくれていました。
それを知ったあなたは、当然それを使って実装したくなりました。

ここで登場するのが、git rebaseコマンドです!
上流ブランチの変更に追いつきましょう。
ここでいう上流ブランチとは、feature/<タスク1>ブランチの作成元になったブランチのことです。つまりdevelopブランチのことですね。

まずローカルのdevelopブランチを最新にします。

# developブランチに移動
git checkout develop
# ローカルのdevelopブランチを最新に
git fetch
git merge develop

では、早速作業中のfeature/<タスク1>ブランチに移動して、git rebaseコマンドを実行します。

# feature/<タスク1>ブランチに移動
git checkout feature/<タスク1>
# 上流ブランチの変更に追いつく
git rebase develop

こうすることで、ツリーは以下のようになります。

元々、コミットYの時点のdevelopブランチから切っていましたので、あなたのブランチはそこから分岐していました。
rebaseコマンドを実行したことで、Bさんによって最新の更新されたコミットDから分岐するように、元が変わりました

rebase_explanation_3.png

ちなみになぜコミットAとBにダッシュがついているかというと、
コミットのハッシュ値つまり、git logした時に最初に表示される値が変わっているからです。

こうすることで、Bさんが実装した共通処理をあなたも利用できるようになりました!

これが1つ目のユースケースです。

ブランチの親を変更することで、上流ブランチの変更を取り込みます。

ここでこう思った人もいるのではないでしょうか?

git mergeコマンド」と何が違うのか?
私はmergeとrebaseの違いは、コミットログを綺麗に保ちたいかどうか、がもっとも大きな違いだと理解しています。

違いについては、いろんな記事で紹介されているので、説明しませんが、リンクだけ載せておきます。
ブランチの統合
人によってわかりやすい説明は違うと思いますが、私はこれがわかりやすかったです!

よくrebaseコマンドはコミット履歴を書き換えるため、使うなと言われたりします。私もちゃんと調べるまではヤバいコマンドという印象が強かったです。
ただ危険なのは、以下を守らなかった場合の話だと思っています。

  • 自分の作業ブランチでのみ実行する(ここでは、feature/<タスク名>ブランチ)

つまり他の人と共有しているブランチで実行しない(ここでは、maindevelopブランチ)ようにすればいいのです。

これを守って使えば、rebaseコマンドは、コミットログを綺麗に保つのに役立ちます。
それによってレビュワーやその他の人がコミットログの確認をしやすくなります。

2. 履歴を整理する

2つ目のユースケースは、コミットログを整理する時です。

ではよくあるであろう具体的な場面を考えてみます。

① あなたはfeature/<タスク1>ブランチでの実装を終えたので、リモートにプッシュしました。
いつも通りプルリクエストを作成し、Bさんにレビューをお願いします。

② Bさんがプルリクエストを確認してみると、
たった1行の変更ですが、コミットを3回も行っていることがわかりました。(ここではあえて極端な例を出しています)
さすがにコミットをわけすぎです。そこでBさんはあなたにコミットをまとめてから、再度プッシュするようにお願いしました。

rebase_explanation_4.png

ここでgit rebaseコマンドを使います!

具体的には、コミットA'、コミットB'、コミットEの3つを1つのコミットにまとめます。

とここで、前の例に沿うとコマンドの紹介をするところですが、
コマンドの実行方法については、以下の記事が大変詳しいですので、そちらの方の記事をご確認ください。:bow:
git rebase についてまとめてみた

ここでは、コミットをまとめたいので、squashまたはfixupを使います。

コミットメッセージを破棄するかどうかで使うコマンドを決めます。

そうすると、コミットが1つにまとまり、ツリーは以下のようになります。

rebase_explanation_5.png

めでたくコミットが1つにまとまりました!

では再度プッシュしましょう。
とここで注意です。今回は、既にプルリクエストを出した後にコミットをまとめたいので、

リモートの履歴を書き換えることになります。

その場合、--force-with-leaseオプションを忘れずに付けましょう。

そもそもrebaseコマンドは、1つ目のユースケースでも2つ目のユースケースでも、リモートにプッシュ前に行うのが原則だと思います。
まだリモートにプッシュしていない場合は、上のオプションは使いません。

ただそうもいかない場合があります。それが2つ目のユースケースのような場合です。

そういった場合は、以下のようなコマンドを実行しましょう。

git push --force-with-lease origin feature/<タスク1>

このオプションについては、以下がわかりやすいです。
git push -f をやめて --force-with-lease を使おう

引用させていただくと

PUSHの際、ローカルrefとリモートrefを比較しローカルが最新か判定し、最新でなければPUSHが失敗するというもの。

というものです。間違っても-fは使ってはいけません。

ただし、--force-with-leaseオプションを使う場合も、以下に注意です。

※ただし、直前にfetchしているとPUSHが成功してしまうので注意

直前にfetchしないように!です。
-fを使用した場合と結果が同じになってしまいます。つまり他人の実装を吹き飛ばす可能性があります・・・

以上で、rebaseコマンドの2つ目ユースケースの説明は終わり・・・にしたいですが、
引っかかっていることがあるかもしれません。

最初に書きましたが、2つ目のユースケースは「履歴を整理する」です。
整理、というからにはコミットをまとめる以外のことも行うことができます。

ただ、今回はそれについては詳細は割愛します。
最もよく使うケースは、コミットをまとめるだと思います。

一応、何ができるかを列挙すると

  • 過去のコミットをまとめる(今回説明した内容)
  • 過去のコミットを削除する
  • 過去のコミットを修正する
  • コミットメッセージをまとめて変更する

だそうです。先程貼った記事から引用させていただきました。
他にもできることがあるようですが、大体列挙した中にあるのではないでしょうか?

まとめ

以上、rebaseコマンドについて自分なりに整理してみました。

コマンド自体やオプションについて詳しく説明してくださっている方はたくさんいたのですが、
なぜこれを使うのか、を中心に説明してくださっている記事があまりなかった気がしたので、調べながらこの記事を書いてみました。

gitを使い慣れている人にとっては、当然な話とは思うのですがこの記事を書くことで自分の理解も深まりました。
rebaseコマンドを初めて使おうとしている人の参考になれば幸いです。:blush:

まだまだgitに関しては知らないことも多いと思っているので
誤り等ありましたら、コメントで教えていただけると大変ありがたいです。

13
10
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
13
10