はじめに
エンジニアになって1年ちょっと。流石にGitにも慣れてきた!と思ってました。
つい2週間前までは・・・
これまでコミットは少ないよりも多い方がリバートしやすくて良い、と思っていたのですが
どれがあなたが対応した内容なのかわからない、細かすぎて見づらい、と言われました。
わかりづらいので、rebase
してからプッシュしてくださいと言われたのですが
なんだそれ、の状態・・・
rebase
する際に使うコマンドは調べればたくさんヒットするのですが、
どういうコマンドで、なぜこれを使って、使うとどうなるのかがいまいち理解できませんでした。
rebaseは今までコミットしてきた内容を改変することができ、初心者が使うにはハードルが高いコマンドの印象だったのでよくわからないのに使いたくありませんでした。
そんなとき、最近とある本を読んでやっと理解した気になったので、それについて書きます。
説明しないこと
この記事では、rebaseコマンドのユースケースについて説明することが目的のため、
コマンドのオプションなどはあまり説明致しません・・・
rebase
とは
英単語の意味を覚えてから技術的な意味を理解するのが、個人的な用語の覚え方です。
こちらによると、rebaseは動詞で3つの意味があるそうです。
- To replace the base of a denture.(入れ歯を取り替えること)
- To modify core data from which other data is derived in such a way that the final meaning is unchanged.(最終的な意味が変わらないように、他のデータが派生するコアデータを変更すること)
- To change the base address of.(ベースアドレスを変更します。)
1でないのは明らかですね。 おそらく2ですかね。
大体理解できたところで、git rebase
コマンドを打つときの意味を調べてみると
ブランチの親を変える
ということでした。(書籍「独習Git」より)
これだけではよくわからないので、実際にユースケースを見ながら使い方を理解したいと思います。
ユースケースは2つ
rebase
コマンドを使いたい場面というのは、主に2ケースです。
先ほどrebaseに関する記事はたくさんあると書いたのですが、ユースケースが2つあることを書いている記事が日本語では多くない気がしました・・・
1. 上流に追いつく
英語の意味や「ブランチの親を変える」という意味に沿っているのは、こちらのユースケースですね。
上流の変化に対応する
ために使用する場合。(書籍「独習Git」より)
では具体的な例を考えてみます。
あなたは複数人の開発者が関わる開発に関わっています。
ブランチの運用は以下のようになっています。
ブランチ名 | 用途 |
---|---|
main | developブランチからマージされる。基本直接プッシュはしない。 |
develop | feature/<タスク名>ブランチからマージされる。基本直接プッシュはしない。 |
feature/<タスク名> | developブランチから切って作成されるブランチ。作業は基本的にこのブランチで行う。 |
よくある運用だと思います。
実際はもっと細かくあると思いますが、本題ではないので詳しくは踏み込みません。
では以下のような場面を考えてみます。
① あなたは、develop
ブランチから作成した、feature/<タスク1>
ブランチで作業をしています。
また、開発者Bさんもdevelop
ブランチから作成した、feature/<タスク2>
ブランチで作業をしています。
② Bさんはタスク2の実装が完了したので、develop
ブランチへのプルリクエストを出しました。
見事レビューが通りdevelop
ブランチにマージされました。
③ 幸運なことに、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から分岐するように、元が変わりました。
ちなみになぜコミットAとBにダッシュがついているかというと、
コミットのハッシュ値つまり、git log
した時に最初に表示される値が変わっているからです。
こうすることで、Bさんが実装した共通処理をあなたも利用できるようになりました!
これが1つ目のユースケースです。
ブランチの親を変更することで、上流ブランチの変更を取り込みます。
ここでこう思った人もいるのではないでしょうか?
「git merge
コマンド」と何が違うのか?
私はmergeとrebaseの違いは、コミットログを綺麗に保ちたいかどうか、がもっとも大きな違いだと理解しています。
違いについては、いろんな記事で紹介されているので、説明しませんが、リンクだけ載せておきます。
ブランチの統合
人によってわかりやすい説明は違うと思いますが、私はこれがわかりやすかったです!
よくrebase
コマンドはコミット履歴を書き換えるため、使うなと言われたりします。私もちゃんと調べるまではヤバいコマンドという印象が強かったです。
ただ危険なのは、以下を守らなかった場合の話だと思っています。
- 自分の作業ブランチでのみ実行する(ここでは、
feature/<タスク名>
ブランチ)
つまり**他の人と共有しているブランチで実行しない(ここでは、main
やdevelop
ブランチ)**ようにすればいいのです。
これを守って使えば、rebase
コマンドは、コミットログを綺麗に保つのに役立ちます。
それによってレビュワーやその他の人がコミットログの確認をしやすくなります。
2. 履歴を整理する
2つ目のユースケースは、コミットログを整理する時です。
ではよくあるであろう具体的な場面を考えてみます。
① あなたはfeature/<タスク1>
ブランチでの実装を終えたので、リモートにプッシュしました。
いつも通りプルリクエストを作成し、Bさんにレビューをお願いします。
② Bさんがプルリクエストを確認してみると、
たった1行の変更ですが、コミットを3回も行っていることがわかりました。(ここではあえて極端な例を出しています)
さすがにコミットをわけすぎです。そこでBさんはあなたにコミットをまとめてから、再度プッシュするようにお願いしました。
ここでgit rebase
コマンドを使います!
具体的には、コミットA'、コミットB'、コミットEの3つを1つのコミットにまとめます。
とここで、前の例に沿うとコマンドの紹介をするところですが、
コマンドの実行方法については、以下の記事が大変詳しいですので、そちらの方の記事をご確認ください。
git rebase についてまとめてみた
ここでは、コミットをまとめたいので、squash
またはfixup
を使います。
コミットメッセージを破棄するかどうかで使うコマンドを決めます。
そうすると、コミットが1つにまとまり、ツリーは以下のようになります。
めでたくコミットが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
コマンドを初めて使おうとしている人の参考になれば幸いです。
まだまだgitに関しては知らないことも多いと思っているので
誤り等ありましたら、コメントで教えていただけると大変ありがたいです。