Dev Study
← 解説「rebase — なぜ履歴が「書き換わる」のか」に戻る

サンプルコードで身につける: rebase — なぜ履歴が「書き換わる」のか

解説で学んだ概念を、5つの具体例で確認します。素朴な例から実務寄りの例へと進みます。

1rebaseでハッシュが変わる瞬間(実機の記録)

実際にrebaseして前後を記録した出力です。変更内容(notes.txt を1行追加)は全く同じなのに、parentが変わったためコミットのIDが別物になっています。これがrebaseの本質です。

# rebase する前
$ git rev-parse HEAD
834a495cf6979b968683d61dc22f2a654f2214c6
$ git rev-parse HEAD^
dd2818b6f87da96757d1cd70f43e293660aece49    ← 親は dd2818b

$ git rebase main

# rebase した後
$ git rev-parse HEAD
bacb6891d489f7fbf0c0c81ca4464a4b77539306    ← 別のコミットになった
$ git rev-parse HEAD^
459f52836739f6ed2cd608b98559f1662b75aa61    ← 親が付け替わった

# 変更内容は同じ
$ git show --stat --format='' HEAD
 notes.txt | 1 +

# → 差分は同じでも、parentが違うのでハッシュが変わる

2なぜハッシュを維持できないのか

commitのIDは parent 行を含むテキスト全体から計算されます。だから親を付け替えると、必ず別のIDになります。「変えないでほしい」と願っても原理的に無理だと計算で確認できます。

Python

31つ書き換えると、その先が全部作り直される

3つ連なったコミットの真ん中を書き換えると、それ以降も芋づる式に変わります。rebase -i で古いコミットを編集したときに「そこから先が全部別IDになる」理由がこれです。

Python

4古いコミットは消えていない

rebase後も、元のコミットはオブジェクトDBに残っています。参照されなくなっただけです。reflogでハッシュを見つければ、いつでも元の状態に戻せます。

# rebase後、古いコミットはどうなったか
$ git cat-file -t 834a495cf6979b968683d61dc22f2a654f2214c6
commit                        ← まだDBに存在する

# ただし、どのブランチからも辿れなくなっている
$ git branch --contains 834a495
(何も出ない)

# reflog を見れば、HEADが過去に指した先が分かる
$ git reflog
bacb689 rebase (finish): returning to refs/heads/topic
bacb689 rebase (pick): メモを追加
459f528 rebase (start): checkout main
834a495 commit: メモを追加          ← rebase前の状態

# 元に戻したいとき
$ git reset --hard 834a495

5mergeとrebaseで履歴の形がどう変わるか

同じ作業をmergeした場合とrebaseした場合で、履歴の見え方を並べます。どちらが正しいという話ではなく、「既存コミットを残すか、作り直すか」という性質の違いです。

# ── merge した場合 ─────────────────
$ git log --graph --oneline
*   9f3c1a2 Merge branch 'feature'
|\
| * e4d92c8 ログイン処理を追加     ← IDは変わらない
* | 0361ab2 ログアウト処理を追加
|/
* dd2818b READMEに使い方を追加

#   分岐と合流が事実として残る。既存コミットのIDは不変

# ── rebase した場合 ────────────────
$ git log --graph --oneline
* 7b2e5f1 ログイン処理を追加       ← e4d92c8 から作り直された
* 0361ab2 ログアウト処理を追加
* dd2818b READMEに使い方を追加

#   一直線で読みやすい。ただしIDは別物になっている
#   → pushしていない自分だけのブランチなら rebase して良い
#     共有済みブランチは merge にする