Gitで開発中のブランチを親ブランチに追従させるために rebase したところ、かなり嫌な崩れ方をした。
起きたのはこんな症状だった。
- 前に消したはずのファイルがステージングに上がる
- 修正済みのファイルが消えて、古い内容が復活する
- 階層移動したはずのファイルが、昔の場所で復活する
- VS Code 上では何が正なのか分かりづらい
最初は VS Code 側の不具合かと思ったが、整理すると本質は Git の rebase による履歴再適用時の衝突だった。
原因として考えられること
今回のような「削除したものが戻る」「移動前のファイルが復活する」は、主に以下で起きやすい。
1. 親ブランチ側でも同じファイルに変更が入っていた
自分は削除・移動したつもりでも、親ブランチ側が古いパスのまま修正していると、rebase 時に古いファイルが再適用されやすい。
2. Git が rename / move をきれいに認識できなかった
Git はファイル移動を厳密に記録しているわけではなく、実質的には「削除 + 追加」として扱うことがある。
そのため、階層移動と中身修正を同時にやると、rebase 時に壊れやすい。
3. 競合解消時に古い側を採ってしまった
VS Code の競合解消は便利だが、Current / Incoming の意味を曖昧に理解したまま進めると、意図せず先祖返りが起きる。
特に rebase 中は感覚がずれやすい。
4. 競合が片付ききっていない状態で add / continue した
git add . でまとめて進めると、不要に復活したファイルまでステージングされやすい。
まずやるべきこと
rebase 中に明らかに壊れたなら、無理に直しきろうとしない方がよい。
未 push なら、まずは rebase 前の状態に戻すのが安全。
rebase の途中ならこれ
git status
git rebase --abort
これで、競合や中途半端なステージングを含めて、rebase 開始前の状態に戻せることが多い。
もし abort できない場合
すでに rebase 自体は終わっていて、履歴だけ書き換わってしまっている可能性がある。
その場合は reflog から rebase 前の地点を探して戻す。
git reflog --date=local
git reset --hard HEAD@{n}
HEAD@{n} は、rebase 開始前の位置を見て選ぶ。
今回の学び
一番大きかったのは、壊れた状態のまま rebase を進めないこと。
「とりあえず競合を解消して continue すれば何とかなるだろう」は危険だった。
特に、以下が含まれるコミットは要注意。
- ファイル削除
- ディレクトリ移動
- ファイル名変更
- import パス変更
- 中身の大幅修正
これらを一気にやると、rebase でかなり崩れやすい。
今後の安全な rebase 手順
今後は次の流れでやる。
1. 作業ツリーをきれいにする
未コミット変更があるなら stash か commit で退避する。
git status
git stash push -u -m "before rebase"
2. 念のため退避ブランチを切る
git switch feature/xxx
git branch backup/feature-xxx-before-rebase
3. リモート最新を取得
git fetch origin
4. 自分のブランチで親ブランチに rebase
git rebase origin/main
親が develop なら origin/develop にする。
5. 競合したら1件ずつ直す
git status
git diff
git add path/to/file
git rebase --continue
ここで大事なのは、git add . を雑に使わないこと。
ファイル単位で確認して進める方が安全。
6. 変だと思ったら即 abort
git rebase --abort
7. 終わったら差分確認
git status
git log --oneline --graph --decorate -20
git diff origin/main...HEAD
再発防止のために意識したいこと
移動と修正を分ける
安全なのは以下の順番。
- ファイル移動だけ
- import 修正だけ
- 中身修正だけ
- 不要ファイル削除
この分け方にすると、Git が履歴を追いやすい。
長期間放置してから rebase しない
親ブランチとの差が広がるほど、競合も崩れ方も重くなる。
細かく追従した方が結果的に楽。
VS Code 任せにしすぎない
競合が起きたら、最終的には git status と git diff で確認する。
GUI は便利だが、壊れたときの本当の状態は Git コマンドの方が分かりやすい。
まとめ
今回の件で分かったのは、rebase 自体が悪いのではなく、削除・移動・改名・修正が混ざった履歴に対して、曖昧なまま進めると事故るということだった。
そして、壊れたときは慌てて直そうとするより、
- まず
git rebase --abort - 戻れなければ
git reflog - 正常地点に
git reset --hard
この順で落ち着いて戻すのが一番安全。
今後は、rebase 前に退避、競合は1件ずつ確認、怪しかったら即 abort の運用に寄せていきたい。