Gitで作業していると、
「このファイル、そもそもコミットしたこと自体をなかったことにしたい」
というケースがあります。
単純に現在のブランチからファイルを削除するだけなら git rm してコミットすれば済みます。しかし、それでは過去のコミット履歴にはファイルが残ったままです。
この記事では、interactive rebase(git rebase --committer-date-is-author-date -i)を使って、特定のファイルに関する変更を過去のコミットから取り除く方法を紹介します。
なお、この方法はコミット履歴を書き換えます。すでにリモートへ push しているブランチや、複数人で共有しているブランチで実行する場合は注意してください。
1. 対象ファイルに関係するコミットを探す
まず、削除したいファイルがどのコミットで追加・変更されたのかを確認します。
ファイルパスがわかっている場合は、次のコマンドを実行します。
git log --oneline --name-status -- path/to/file
対象ファイルの正確な名前やパスがわからない場合は、コミットと変更されたファイルをまとめて確認します。
git log --oneline --name-status
ここで重要なのが、ファイルの左側に表示されるステータスです。
A path/to/file
M path/to/file
主に確認するのは次の2つです。
A:ファイルが新規追加されたM:既存ファイルが変更された
後ほど、この違いによって対応方法が変わります。
2. 一番古い対象コミットからrebaseする
対象ファイルに関係するコミットを洗い出したら、その中で一番古いコミットを確認します。
そのコミットを earliest_commit_id とすると、次のように interactive rebase を開始します。
git rebase --committer-date-is-author-date -i {oldest_commit_id}^
^ を付けているのは、対象コミット自身を rebase の範囲に含めるためです。
たとえば対象となる最古のコミットが abc1234 なら、
git rebase --committer-date-is-author-date -i abc1234^
となります。
3. 対象コミットをdropまたはeditに変更する
コマンドを実行すると、エディターが開いてコミット一覧が表示されます。
pick abc1234 add config
pick def5678 update config
pick ghi9012 implement feature
ここから、対象ファイルに関係するコミットを修正していきます。
そのファイルだけを変更しているコミットの場合
そのコミット自体が不要なので、pick を drop に変更します。
drop abc1234 add config
これによって、そのコミット自体が履歴から削除されます。
ただし、同じコミットに残したい変更が含まれている場合は drop してはいけません。
他のファイルの変更も含まれている場合
対象ファイル以外の変更は残したいので、コミットそのものを削除することはできません。
その場合は pick を edit に変更します。
edit abc1234 implement feature
rebase を開始すると、そのコミットのところで処理が一時停止します。
そこで、対象ファイルの変更だけを取り除きます。
4. editしたコミットから対象ファイルの変更を取り除く
ここからは、そのコミットで対象ファイルを新規追加したのか、既存ファイルを変更したのかによって操作が変わります。
ファイルを新規追加していた場合
そのコミットでファイル自体を新しく追加していたのであれば、ファイルを削除します。
git rm path/to/file
その後、コミットを作り直します。
git commit --amend
必要に応じてコミットメッセージも修正します。
既存ファイルを変更していた場合
そのコミットで対象ファイルに変更を加えていた場合は、対象ファイルだけをそのコミットの直前の状態に戻します。
git restore --source={commit_id}^ --staged --worktree -- path/to/file
たとえば対象コミットが abc1234 なら、
git restore --source=abc1234^ --staged --worktree -- path/to/file
とします。
これによって、そのコミットに含まれていた対象ファイルの変更を取り除くことができます。
続いてコミットを修正します。
git commit --amend
コミットメッセージに対象ファイルについての記述があれば、ここで適宜修正します。
5. rebaseを続行する
コミットの修正が終わったら、rebaseを続けます。
git rebase --continue
edit にしたコミットが複数ある場合は、次の edit の位置で再び処理が停止します。
そのたびに、
- 対象ファイルの変更を取り除く
git commit --amendするgit rebase --continueする
という作業を繰り返します。
すべて処理できれば rebase は完了です。
最後に履歴を確認する
rebase が完了したら、対象ファイルに関する履歴が意図したとおりになっているか確認します。
git log --name-status -- path/to/file
あわせて、
git status
や、
git log --oneline
などでブランチ全体の状態も確認しておくと安心です。
注意:すでにpushしている場合
git rebase --committer-date-is-author-date -i は過去のコミットを書き換える操作です。
そのため、対象コミットをすでにリモートへ push している場合、rebase 後はローカルとリモートでコミットIDが異なる状態になります。
リモート側にも書き換え後の履歴を反映する必要がある場合は force push が必要になります。
git push --force-with-lease
--force よりも、他の人がリモートへ追加した変更を誤って上書きしにくい --force-with-lease を使うほうが安全です。
ただし、共有ブランチの履歴を書き換えると他の開発者にも影響します。チームで使用しているブランチの場合は、実行前に影響範囲を確認しておきましょう。
まとめ
特定のファイルに関する変更だけをコミット履歴から取り除きたい場合は、次の流れになります。
対象ファイルに関係するコミットを調べる
↓
一番古いコミットから git rebase --committer-date-is-author-date -i
↓
不要なコミットなら drop
↓
他の変更も含むなら edit
↓
対象ファイルの変更だけ取り除く
↓
git commit --amend
↓
git rebase --continue
ポイントは、コミット単位で消してよいなら drop、コミットの一部だけ消したいなら edit と使い分けることです。
また、edit の場合は対象ファイルがそのコミットで「新規追加されたもの」なのか「既存ファイルへの変更」なのかを確認してから操作します。
interactive rebase を使えば、他の変更を残したまま、特定のファイルに関する変更だけを過去のコミットから取り除くことができます。