Gitからファイルを完全に削除する方法（BFG Repo-Creaner）

# やっべ！間違えたファイルをgit pushしちゃった！
この記事では誰もが一度はやらかすであろう（？）、Gitレポジトリから"ヤバい"ファイルを消し去る方法を解説します。筆者は主に趣味でプログラミングをやる際にGitを使用するため、業務等の視点から見ると、不適切な点があるかもしれません。

以下で使用している、`git push -f`は使い所を間違えると非常に危険なコマンドです。なぜ危険なのかわかるまでは使わないほうが無難だと思います。どうしても使わないとならないときは、仕事ならGitのエキスパートが身近にいると思うので、その人に助言を求めましょう。

## 対象となる事例
この記事は、以下のような種類の情報を含んだファイルを、コミット＆プッシュしてしまった人が、Gitレポジトリから過去にさかのぼって削除する方法についてまとめました。

- 秘密鍵
- パスワード
- 個人情報
- etc...

## 対象でない事例
以下のような場合、完全な削除は混乱の原因となるはずなので、履歴を参照できる変更方法が望ましいです。

ファイルサイズが巨大なもの等に関しては要相談かもしれませんが、筆者自身が扱ったことがないのでこの記事ではスコープ外とします。

- これまで使っていたファイルを消したい (-> `git rm`)
- ソースコードの変更を取り消したい (-> `git revert`)
- 一気にしてしまったコミットを分割したいor逆にまとめたい(-> `git rebase`)
- etc...

# 注意点
以下の方法で削除した情報も、実際にレポジトリをクローンしたり、Githubのキャッシュから見られる可能性があります。一度リモートに上がってしまったパスワードなどの情報はちゃんと変更しましょう。

また、この記事ではBFG公式の手順とは少し異なる手順で作業をしています。現在存在しているファイルを削除、コミット、履歴から削除という手順を取るためです。気になる方は、参考資料からBFG公式ページを読んでみてください。

# `bfg` vs `git filter-branch`
[機密データをリポジトリから削除する - Githubヘルプ](https://help.github.com/ja/github/authenticating-to-github/removing-sensitive-data-from-a-repository)

BFG Repo-Cleanerは、Javaで書かれたオープンソースのツールで、Gitから不要なデータを削除することに特化したツールです。filter-branchより**10~720倍**高速らしいです（BFGのReadmeより）。Macであれば、Homebrewでインストールできます。

対する`git filter-branch`はGitをインストールすれば使えるようになる、Gitのサブコマンドの一つです（addやcommitのように）。ファイルやデータだけでなく、コミット者の名前や、メールアドレスの変更にも対応しています。筆者は、間違った個人用メールアドレスでコミットしてしまったものを変更する際に使用したことがあります。

今回は、シンプルな操作ですむbfgを使用します。`git filter-branch`は高機能ですがオプションがいっぱいで辛いです。

# 黒歴史クリーニングの時間です
以下`$`はその行をシェルに入力することを表します。

password.txtというファイルを削除してみます。

## 環境

```
macOS Catalina 10.15.2
git version 2.20.1
bfg 1.13.0
```

## 下準備

### 1. リポジトリのクローン or リセット
すでにローカルにある場合でも、普段の作業用ローカルレポジトリとは別にクローンしておくのがおすすめです。クリーンな状態で作業できるのと、やらかしたときにやり直しやすいので。

また、ローカルのこれまでの作業リポジトリが一種のバックアップ担ってくれます。

```
$ git clone github:username/reponame.git dirname
```

### 2. bfg のインストール
```
$ brew install bfg
```

依存パッケージとしてjavaがインストールされます。

それ以外の場合はBFGのの公式サイトからjarファイルをダウンロードして実行すればOKです。`java -jar bfg.jar `って感じで。

### 3. 消し去りたいファイルの`git rm`
(もうファイルを消去している場合、この手順は不要です。)
BFGはデフォルトで現在チェックアウトされている状態を保護します。消したいファイルに、とりあえずのサヨナラを告げておきましょう。

```
$ git rm password.txt
$ git commit -m "Delete password.txt"
```

## 本調理

### 4. BFGを使用してリポジトリからデータを削除
パスではなくてファイル名で指定しろ、とのことです。

```
$ bfg -D password.txt
...
...
BFG run is complete! When ready, run: git reflog expire --expire=now --all && git gc --prune=now --aggressive 
```

### 5. 後処理
上で言われたコマンドを実行して、reflog（HEAD等の移動ログらしい）を更新してあげましょう。

```
$ git reflog expire --expire=now --all && git gc --prune=now --aggressive 
```

### 6. リモートにプッシュ
ただ`git push`すると、以下のように怒られます。これは、過去のコミットの内容を変更しているために、リモートとローカルのリポジトリで整合性が取れないためです（歴史改変しているので当たり前）。

```
$ git push
To github.com:alcnaka/bfg-practice.git
 ! [rejected]        master -> master (fetch first)
error: failed to push some refs to 'git@github.com:alcnaka/bfg-practice.git'
...
...
```

ここで、登場するのが`git push -f`です。これはForce pushといって、ローカルで改変した歴史を強制的にリモートに上書きするコマンドです。上述した通り、とても危険なコマンドなので、事前に`git remote -v`するなどして、プッシュ先を確認しておきましょう。

```
$ git push -f
...
...
To github.com:alcnaka/bfg-practice.git
 + c8c7f66...605f550 master -> master (forced update)
```

### 7. さいごに

作業につかったリポジトリはこの先も使えますが、従来からあるローカルのリポジトリは、使うことができなくなります。

一番手っ取り早いのは、ディレクトリごと削除して、新しくcloneしてくることですね。

# 確認
これが変更前のもの。
<img width="1007" alt="変更前" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/293792/c61be7e0-e6b7-8dd7-c43c-87ca79eef6f3.png">

これが変更後のもの。
<img width="672" alt="変更後" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/293792/5d94c9d3-2763-787b-f185-81c6aff61052.png">

ファイルが追加されたことがなかったことになっているので、もちろん内容を見ることはできなくなっています。変更内容が変わったことによって、コミットのハッシュ値が変わって`git push -f`が必要になるんですね。

からっぽのコミット自体は残ってしまいます。

[サンプルレポジトリ](https://github.com/alcnaka/bfg-practice)

# あとがき

Qiit初投稿で、見づらい部分等あったら教えてください。

本来なら、こういう情報は`git add`しないように管理するべきです。そして、`git push`する前に確認しましょう。そもそも、業務ならそのへんのルール等はあるのかな。

仮にも一度インターネット上に上がってしまったデータですので、本当にクリティカルなデータの場合、GitHubサポートに連絡すべきですね。

最後まで読んでいただきありがとうございました。間違い等あれば、指摘していただけると嬉しいです。

# 参考資料

[機密データをリポジトリから削除する - GitHub](https://help.github.com/ja/github/authenticating-to-github/removing-sensitive-data-from-a-repository)
[BFG Repo-Cleaner](http://rtyley.github.io/bfg-repo-cleaner/)