rebase を理解する
このページで分かること
- rebase が「コミットを別の土台の上に付け替える」操作だと説明できる
git rebase mainで枝分かれを1本に並べ直せる- rebase するとコミットの ID が変わる理由が分かる
- 共有したコミットを rebase してはいけない理由と、取り消し方が分かる
このページの目次9 items
23と24では、枝分かれした履歴をマージで1つに合わせました。このページでは、もう1つの取り込み方である rebase(リベース)を扱います。rebase は、ブランチのコミットを付け替えて、履歴を1本にまっすぐ並べ直す操作です。
rebase は「土台を付け替える」#
rebase の base は、ブランチが枝分かれした根元のコミット、つまり土台のことです。re-base は「土台を付け替える」という意味です。
練習用のリポジトリで、枝分かれした状態を作ります。ブランチでプリンのレシピを2回に分けて書き、その間に main でもコミットします。
cd ~/git-practice
git switch -c recipe-pudding
echo "卵 3個" > recipes/pudding.txt
git add recipes/pudding.txt
git commit -m "プリンのレシピを追加"
echo "牛乳 300ml" >> recipes/pudding.txt
git commit -am "プリンに牛乳を追加"
git switch main
echo "ゴミを出す" >> todo.txt
git commit -am "やることにゴミ出しを追加"
git log --oneline --decorate --graph --all -n 5* 66549f1 (HEAD -> main) やることにゴミ出しを追加
| * ad7f9e8 (recipe-pudding) プリンに牛乳を追加
| * 5cf83e1 プリンのレシピを追加
|/
* 52db35e Merge branch 'oat-milk'
|\ recipe-pudding の土台は 52db35e です。そのあと main は 66549f1 に進みました。ここでマージすれば23と同じ 3-way マージになりますが、rebase ではプリンの2つのコミットを、main の最新 66549f1 の上に乗せ直します。
git rebase を実行する#
rebase は、付け替えたいブランチに立ってから、新しい土台を指定します。マージ(取り込まれる側に立つ)とは向きが逆なので注意してください。
git switch recipe-pudding
git rebase mainSuccessfully rebased and updated refs/heads/recipe-pudding.git log --oneline --decorate --graph --all -n 4* 7137480 (HEAD -> recipe-pudding) プリンに牛乳を追加
* e0c6b9b プリンのレシピを追加
* 66549f1 (main) やることにゴミ出しを追加
* 52db35e Merge branch 'oat-milk'
|\ 枝分かれが消え、プリンの2つのコミットが main の続きとして1本に並びました。ブランチを作ったのが「ゴミ出しのあと」だったかのような履歴です。
ここで main に取り込むと、23の Fast-forward になります。
git switch main
git merge recipe-puddingUpdating 66549f1..7137480
Fast-forward
recipes/pudding.txt | 2 ++
1 file changed, 2 insertions(+)
create mode 100644 recipes/pudding.txtマージコミットを作らずに取り込めました。「rebase してから Fast-forward でマージする」のが、rebase のいちばん典型的な使い方です。
git branch -d recipe-puddingなぜ ID が変わるのか#
rebase の前後で、同じ「プリンのレシピを追加」の ID が 5cf83e1 から e0c6b9b に変わっています。
第3部の17で見たとおり、コミットには親のコミットの ID が書き込まれていて、コミットの ID はその中身全体から計算されます。rebase で親が 52db35e から 66549f1 に変われば、中身が変わるので ID も変わります。つまり rebase は、元のコミットを動かしているのではなく、同じ変更を持つ新しいコミットを作り直しているのです。
途中経過は reflog で確かめられます(第2部の15)。
git reflog -n 57137480 HEAD@{0}: rebase (finish): returning to refs/heads/recipe-pudding
7137480 HEAD@{1}: rebase (pick): プリンに牛乳を追加
e0c6b9b HEAD@{2}: rebase (pick): プリンのレシピを追加
66549f1 HEAD@{3}: rebase (start): checkout main
ad7f9e8 HEAD@{4}: checkout: moving from main to recipe-pudding共有したコミットは rebase しない#
ID が変わることから、rebase には大事な決まりが1つあります。
rebase してよいのは、まだ自分の手元にしかないコミットです。GitHub に送る(第6部の39の push)前のブランチなら、自由に rebase して構いません。
rebase の途中でコンフリクトしたら#
rebase はコミットを1つずつ作り直すので、その途中でコンフリクトすることがあります。止まったときの直し方は24と同じで、ファイルを直して git add までは共通です。そのあとが少し違います。
| コマンド | 動き |
|---|---|
git rebase --continue | 直したコミットを作り直し、残りのコミットへ進む(マージの git commit にあたる) |
git rebase --skip | 今のコミットを捨てて、次へ進む |
git rebase --abort | rebase をやめて、始める前の状態に戻す |
マージと rebase、どちらを使うか#
| マージ | rebase | |
|---|---|---|
| 履歴の形 | 枝分かれと合流がそのまま残る | 1本にまっすぐ並ぶ |
| 既存のコミット | 変えない | 作り直す(ID が変わる) |
| 共有後に使えるか | 使える | 使えない |
| コンフリクトの解決 | 1回でまとめて | コミットごとに起きることがある |
どちらが正しいということはありません。「何が起きたかをそのまま残す」のがマージ、「読みやすい形に整えてから残す」のが rebase です。迷ったら、共有した履歴はマージ、自分の手元の作業ブランチを最新の main に追いつかせるときは rebase、と使い分けてください。チームでどちらに揃えるかは、28のブランチ戦略で扱います。
よくある疑問#
Q. git pull --rebase という書き方を見かけました。
A. GitHub などから最新の変更を取り込むときに、マージではなく rebase を使うオプションです。考え方はこのページと同じで、自分のまだ送っていないコミットを、取り込んだ最新の履歴の上に付け替えます。第6部の40で扱います。
Q. main のほうを rebase してもいいですか?
A. main は共有されていることがほとんどなので、避けてください。rebase するのは、自分の作業ブランチを main の上に付け替える方向だけ、と決めておくと安全です。
まとめ#
- rebase は、ブランチのコミットを新しい土台の上に付け替えて、履歴を1本に並べ直す
- 付け替えたいブランチに立って
git rebase main。そのあと main で取り込むと Fast-forward になる - 付け替えたコミットは作り直された別のコミットで、ID が変わる。元のコミットは reflog からたどれる
- 共有したコミットは rebase しない。rebase するのは自分の手元にしかないコミットだけ
- 途中でコンフリクトしたら、直して
git add→git rebase --continue。やめるなら--abort
次のページ#
26. interactive rebase で履歴を整える ── rebase の対話モードで、コミットをまとめる・消す・メッセージを直すなど、共有する前の履歴を整えます。