Rebase and merge(リベース)
変更を統合する際に履歴を綺麗に整えるための機能です。
トピックブランチに対してリベースを行うと最新のベースブランチからあたかもトピックブランチを作成したかのような履歴状態を作り出すことができます。(ブランチの基点となるコミットを別のコミットに移動します。)
なぜ「リベース」と呼ぶのか?
親コミット(ベースとなるコミット)を新たしく付け替えるためです。
マージとリベースの違い
どちらもブランチの統合を行うという意味では同じ動作になります。履歴が一直線になる点が通常のマージとは異なる点になります。
マージとリベースで使い分けではどうすれば良いか。
基本方針
基本的には下記のような方針でマージとリベースを使い分けると良いでしょう。
リベースを使うケース
- プッシュしていないローカルの変更
マージを使うケース
- プッシュしてしまったローカルの変更
- コンフリクトが発生しそうな場合
コンフリクトが発生しそうかの判断方法
他の人の開発内容を確認すること
ローカルの変更をGitHubにプッシュしてプリリクエストを作成してしまう。
GitHubが親切にコンフリクトしているかどうか判定して表示してくれるのでそれで判定できます。
マージのメリット
作業の履歴を細かく残せる。(リベースかマージか選択する上で非常に重要なポイントです。)
マージを使う上で決め手となるのはこの点になります。
コンフリクトの解決が比較的簡単
リベースの場合は、コミットそれぞれにコンフリクトを解消していかなければならないが、マージの場合はまとめて解決すれば良いだけになります。
リベースのメリット
コミットの数が減るので履歴が簡素になる。(マージコミットの数が減る)なので、履歴を簡潔にすることが重視されている場合はリベースを選択すると良いでしょう。
リベースの特徴
メリット
- 仮にトピックブランチの数が増えたとしてもコミット履歴は常に一直線になり見やすくなります。
デメリット
- 過去のコミット履歴を改版する為コミット IDも変わってしまう為、普通の方法ではプッシュができなくなります。
- リベース先のブランチが複数コミットされている場合、1コミットずつ辿っていく挙動になるのでコンフリクトもその都度解消しなければならない点(マージの場合はコンフリクトの解消は1度だけで済みます。)
リベースの禁止事項:以下のようなことが言われています。
GitHubにプッシュしたコミットをリベースしてはいけない
GitHubに一旦プッシュしてしまったブランチをローカルでリベースして再度プッシュしようとしたらプッシュ自体ができなくなってしまいます。理由としては、「リモートで管理されているコミットの親子情報」と「ローカルで管理されているコミットの親子情報」が異なっているとリモートで認識されてしまうためです。
「git push -f」は絶対に使わないこと
これをするとローカルの履歴でリモートの履歴を完全に上書きできてしまいます。しかし、リモートの履歴が壊れてしまうことを意味するのでプッシュできないからと言ってこの手段をとるのは止めるようにしましょう。
ただ、実務では強制pushが基本
ただ、実務では基本的にはfeatureブランチで各個人が作業をしていると思いますし、強制pushしてしまっても問題ないと思います。
featureに対して最新のmasterの内容を取り込む際にrebaseする際も基本最新の状態で取り込んでいるので、これも強制pushしてしまって問題ないかと。
コマンド
1 |
git rebase <コミットの基点としたいブランチ名> |
例
1.トピックブランチの基点をmasterブランチの先に付け替える。
コミットの基点がmasterブランチの先につけ変わるだけでなく、masterブランチの変更内容も取り込まれます。
1 2 |
git checkout <トピックブランチ> git rebase master |
2.masterブランチとトピックブランチをマージする。
ファストフォワードでのマージになるので新しくコミットが作られることはありません。(あくまで自分のブランチのコミットを他人が編集したコミットに向き先を変える動きになります。)
1 2 |
git checkout master git merge <トピックブランチ> |
コミットログを操作
コミットを修正したい場合は下記のコマンドを実行します。
1 |
git rebase -i <コミットID> もしくは HEAD~(やり直したいコミット数) |
すると下記のようにエディタ上に直近のコミットメッセージが表示されます。
1 2 3 |
pick コミットID コミットメッセージ1 pick コミットID コミットメッセージ2 pick コミットID コミットメッセージ3 |
コミットの順番の入れ替え
上記で表示されたエディタ上のコミットメッセージの順番を入れ替えるだけでコミットログの順番まで入れ替えることが可能です。
squashは上の直前のpickに吸収される挙動になるので、離れたコミット同士を統合したいという場合に順番の入れ替えは使えます。
コミットをまとめる。
pick → squashに書き換えることでそのsquashに書き換えたコミットがpickに統合されます。
1 2 3 |
pick コミットID コミットメッセージ1 squash コミットID コミットメッセージ2 squash コミットID コミットメッセージ3 |
vimと同じエディタが開くので編集後は「:wq」で保存をすればrebaseが完了します。
コンフリクトが起きた場合はどうするか?
pick1⇨pick2という感じで、順番にコミットが実行されるのでその際にコンフリクトが起きた場合はrebaseが完了していない扱いになります。
その際は、一つ一つコンフリクトを解消していく作業が必要になります。
1 2 3 |
1.コンフリクト解消 2.git add (対象のファイル) 3.git rebase --continue |
注意点としては、通常のコミットと同じようにgit commit ではなく、git rebase --continueを実行する点です。次のコミットに移動するという意味になります。continueを全て完了して特にコンフリクトが起きなければrebaseは完了し以下のメッセージが出ます。

rebaseやり直したい場合
まだコンフリクトが起きるなどしてrebase中であれば
以下コマンドを実行します。
1 |
git rebase --abort |
すでにrebaseが完了してしまっている場合は
1 2 |
git reflog git reset --hard (戻りたいid) |
上記でgitの変更履歴を遡って戻ります。
コミットを分割する。
分割したいコミットを下記のようにeditにします。
1 |
edit コミットID コミットメッセージ1 |
下記コマンドを実行します。
1 |
git reset HEAD^ |
すると最新のコミットが取り消されてステージングすらもされていない状態にファイルが戻るので、コミットを分割したいファイルごとにステージングに追加したりしてコミットを分割します。コミットが完了したら下記コマンドを実行します。
1 |
git rebase --continue |
この記事へのコメントはありません。