git rebaseとは・git rebaseの使い所
Mergeとの共通点
二つのブランチの内容を一つのブランチにまとめるという点でマージと同様。
また、両者とも実行時にコンフリクトが生じる可能性がある。
Mergeとの違い
- マージは二つのブランチを川の流れのように合流させる。特にひねりがなく直感的には最もわかりやすいと思う。
- リベースはgitの歴史を書き換えて、二つのブランチが枝分かれし始めた地点(ベース)を最新の地点にまで移動させる事で、結果枝分かれした事実そのものをなかった事にするようなもの。
ケーススタディ
- 支流の内容を本流に取り込みたい時(feature-XXXXX -> develop 等)
- 例えば、feature-XXXXXでの開発が一段落し、レビューも完了したので本流となるdevelopブランチに取り込みたい時などに該当する。
-> この場合は本流(develop)に支流(feature-XXXXX)をマージして取り込む。
※ 本流(develop)を支流(feature-XXXXX)にリベースはしてはいけない。なぜならdevelopは複数の人が参照しているのに、履歴が書き変わってしまうからである。
- 本流の内容を支流に取り込みたい時(develop -> feature-XXXXX 等)
- 例えば、developにある重要な変更を今の作業ブランチ(feature-XXXXX)でも使いたいのでdevelopをfeature-XXXXXに取り込みたい時など。
-> 支流(feature-XXXXX)を本流(develop)上にリベースする。
-> developをfeature-XXXXにマージしてもいいが、そうするとコミット履歴で、コミットが時間順に並ぶのでfeature-XXXXXとdevelopの区別がなく分かりにくくなる。rebaseだと手間がかかりすぎる時などにするくらいか。
rebaseの手順
feature-XXXXブランチをdevelopブランチ上にリベースしたいとする。
0: 当たり前だが、リモートにdevelopブランチがある場合は、その最新の変更をローカルに持ってきておく事(origin はリモートのgitリポジトリの名前)。
git pull origin develop
1: feature-XXXXブランチを複製しておく。リベースは履歴を書き換えるため、失敗した時に元に戻すのが手間になってしまうため。なお、直す手順としては、git reflog
を使うこともできるので絶対ではない。git reflogについては記事最下部参照
git checkout -b feature-XXXX-copy
2: feature-XXXXブランチにチェックアウトする。その後いきなりリベースしてもいいのだが、コミットを一つ一つdevelopブランチ上に重ねていく事になるため、何度もマージコンフリクトを解消しなくてはならず面倒くさいことが多い。そのため、まずrebase -i
でfeature-XXXXブランチのコミットを一つにまとめる。
git checkout feature-XXXX
git rebase -i 5634f2e
5634f2e
とは、feature-XXXXとdevelopブランチの共通点(分岐点)となっているコミットのハッシュ値。
3: するとviが起動し、以下のように表示される。
なおコミットの流れは5634f2e
(developブランチとの分岐点) -> 3bf6995
-> 15717d1
-> 6a36aed
の順となっている。
pick 3bf6995 Entity, Repositoryを改修した。
pick 15717d1 正しいアイコン画像を設定した。
pick 6a36aed 不要な記述を消した。
# Rebase 5634f2e..6a36aed onto 5634f2e (3 commands)
#
# Commands:
# p, pick <commit> = use commit
# r, reword <commit> = use commit, but edit the commit message
# e, edit <commit> = use commit, but stop for amending
# s, squash <commit> = use commit, but meld into previous commit
# f, fixup <commit> = like "squash", but discard this commit's log message
# x, exec <command> = run command (the rest of the line) using shell
# b, break = stop here (continue rebase later with 'git rebase --continue')
# d, drop <commit> = remove commit
# l, label <label> = label current HEAD with a name
# t, reset <label> = reset HEAD to a label
# m, merge [-C <commit> | -c <commit>] <label> [# <oneline>]
# . create a merge commit using the original merge commit's
画面に記載してある通りだが、ここではまず3bf6995
-> 15717d1
-> 6a36aed
の3つを一つにまとめて、developブランチとの分岐点である5634f2e
からfeature-XXXXブランチにコミットが一つだけ残るようにしていく。
なお、やっぱり辞めたい場合は:q!
でviを終了すれば、特に変更なくrebaseが終了する。
4: コミットをまとめる
gitではなくviの操作法になるが、a
キー、i
キーなどを押して編集モードに入る。
普通のテキストエディタのような感じで、文字列を下記のように変更する。
pick 3bf6995 Entity, Repositoryを改修した。
squash 15717d1 正しいアイコン画像を設定した。
squash 6a36aed 不要な記述を消した。
# Rebase 5634f2e..6a36aed onto 5634f2e (3 commands)
#
# Commands:
# p, pick <commit> = use commit
# r, reword <commit> = use commit, but edit the commit message
# e, edit <commit> = use commit, but stop for amending
# s, squash <commit> = use commit, but meld into previous commit
# f, fixup <commit> = like "squash", but discard this commit's log message
# x, exec <command> = run command (the rest of the line) using shell
# b, break = stop here (continue rebase later with 'git rebase --continue')
# d, drop <commit> = remove commit
# l, label <label> = label current HEAD with a name
# t, reset <label> = reset HEAD to a label
# m, merge [-C <commit> | -c <commit>] <label> [# <oneline>]
# . create a merge commit using the original merge commit's
2行目・3行目の行頭を「squash」に変えたのがお分かりいただけるだろうか。これは、一つ目のコミット「3bf6995」はそのまま残し、「15717d1」「6a36aed」に含まれる変更を「3bf6995」に合体させることを意味する。
編集終了したら:wq!
で保存してviを終了する。
すると、今度はコミットメッセージ編集画面へ行く。
# This is a combination of 3 commits.
# This is the 1st commit message:
Entity, Repositoryを改修した。
# This is the commit message #2:
正しいアイコン画像を設定した。
# This is the commit message #3:
不要な記述を消した。
# Please enter the commit message for your changes. Lines starting
# with '#' will be ignored, and an empty message aborts the commit.
#
# Date: Thu Jul 16 13:10:41 2020 +0900
#
# interactive rebase in progress; onto 5634f2e
一まとめにしたことがわかるようにメッセージを入力し、再び:wq!
で閉じる。これでコミットのまとめが完了する。
5 developブランチ上にrebaseしていく
一まとめになったコミットをdevelopにリベースする。
git rebase develop
コンフリクトが生じた場合は、それを解消する。これでリベース終了となる。
rebase が完了したが、やっぱり元に戻す(追記 2020/10/16)
feature-XXXXのコピーのブランチを作っていた場合は、そのブランチを正とする形で元にもどすことができる。
コピーのブランチを作っていなかった場合でも、git reflog
が使える。git reflog
とは、HEADがどこを指していたかの記録をログとして残したもので、当然rebaseでHEADがどこに移動したかも記録に残っている。そのため、rebaseもリセットすることができる。
git checkout feature-XXXX
git reflog feature-XXXX
上のコマンドを打ち込むと、reflogが表示される。
<commid id> (HEAD -> feature-XXXX) feature-XXXX@{0}: rebase finished: refs/heads/feature-XXXX onto <commit id>
<commid id> feature-XXXX@{1}: <commit message>
ここでは、feature-XXXX@{0}
がリベース完了時のログ、feature-XXXX@{1}
がリベース前のログである。そのため、この場合は次のコマンドでリベースを元に戻すことができる。
git reset --hard feature-XXXX@{1}
マージコミットも含めてrebaseしたい
git rebase --rebase-merge upstream
参考 git rebase --rebase-margeについて
参考
git rebaseを初めて使った際のまとめ
Git rebaseの使い方
Linuxサーバー管理入門 - gitのrebaseは作業中のブランチにmasterブランチを取り込むためのよい手段 / Git
初心者でもわかる!リベースの使い方を解説します
誤ってリベースしてしまった Git のコミットを元に戻す方法
git rebase --rebase-margeについて