GitHub にパスワードとかセンシティブなファイルを push してしまったときの対処法

`.gitignore` し忘れて他人に見えちゃマズいファイル（パスワードをベタ書きしたファイルや `AWS_SECRET_ACCESS_KEY` を書いたファイルとか）を `git commit` しちゃった！そんなときは 

```bash
$ git reset --hard HEAD~
```

すればすぐ何もなかったことにできます。

が！そこで気付かずに GitHub へ `git push` してしまった！こうなると容易に何もなかったことにはできません。

この記事では、こういうときに何もなかったことにする方法を紹介します。

# そのデータを無効にする
特に Public Repository の場合はすでにそのデータが他人の目に触れていた…ということも十分ありえます。`AWS_SECRET_ACCESS_KEY` なんかは取得用のクローラが存在するとも聞きます。ので、まずは不正利用されても影響が出ないように、__パスワードの書き換えやトークンの無効化__を施しましょう。

（この時点でもう何もなかったことになってない気がする）

# git の履歴から該当のファイルを消す
`git reset` と `git filter-branch` 2つの方法があります。

## `git reset`

__(2015-12-29 15:00 追記)__
`git reset` だとセンシティブファイル以外の作業履歴もすべて消去されてしまうので、それらを残しておきたい場合は後述 `git filter-branch` でコミットを書き換えるようにしてください。
__(追記終わり)__

該当ファイルを `git commit` してすぐ気づいた (3コミット以内) なら、まだ `git reset` で消せます。

```bash
$ git reset --hard HEAD~2 ＃ 消すコミットの数
```

## `git filter-branch`
気づいたのはそこから何コミットもしたあと…だと `git reset` でそこまでの履歴を全部消すのは現実的ではありません。そんな時に役立つのが [`git filter-branch`](https://git-scm.com/docs/git-filter-branch) です。`filter-branch` は普段見慣れない（であってほしい）ですが、__大量のコミットを機械的に書き換える__コマンドです。今回みたいにファイルを消す以外にも、リポジトリ全体のコミットオーサーの書き換えとかも一発でできます。

現在作業中の Pull Request に間違えてコミットしてしまったときは、そのブランチにだけ `filter-branch` を適用するので以下の様なコマンドになります。`/path/to/file_to_rm` は消したいファイル、`1234abcd` はブランチ分岐元のコミットのハッシュ値、`branch_name` は操作対象のブランチ名です。

```bash
$ git filter-branch --force --index-filter \
    'git rm --cached --ignore-unmatch /path/to/file_to_rm' \
    --prune-empty -- 1234abcd...branch_name --first-parent
```

ここで `1234abcd...branch_name --first-parent` は、[`git rev-list`](https://git-scm.com/docs/git-rev-list) というコマンドの引数と同等であり、書き換えるコミットを指定しています。実際に 

```bash
$ git rev-list 1234abcd...branch_name --first-parent
```

を叩いてみれば、Pull Request の Commits に出てくるのと同じコミットハッシュの一覧が得られます。

他のブランチすべてを含めた全体から削除するときは以下のようにします。

```bash
$ git filter-branch --force --index-filter \
  'git rm --cached --ignore-unmatch /path/to/file_to_rm' \
  --prune-empty --tag-name-filter cat -- --all
```

これで履歴から該当ファイルが完全に取り除かれます。

# `git push -f`
日頃忌避される、というか何もないときは__絶対に__叩いてはいけない `git push -f` をここで使います。これしかやりようがないので…。GitHub のリモートリポジトリ上の履歴を書き換えるためです。

```bash
$ git push -f origin branch_name
```

# (2015-12-30 15:10 追記) ローカルリポジトリから完全に痕跡を消す
ここまで `reset` や `filter-branch` した内容ですが、実はローカルリポジトリに __reflog__ という形で作業履歴が残ってしまっています。

[git最強のオプション filter-branch - Qiita](http://qiita.com/Spring_MT/items/f60c391b5dbf569a1d12)

の記事にもあるように、直接コミットハッシュを選択すると中身が見えてしまいます…これらも消しましょう。

```bash
$ rm -rf .git/refs/original/
$ git reflog expire --expire=now --all
$ git gc --aggressive --prune=now
```

これで、ローカルリポジトリからは完全にセンシティブ情報が抹消されました。

（コメントでご指摘くださった @sachin21 さんありがとうございました :bow:）

# 他のチームメンバに手元のリポジトリを取得し直すようお願いする
他のチームメンバの手元にチェックアウトしてあるローカルリポジトリの履歴と、 GitHub 上の履歴は別物になってしまいました。なので、（自分しか触ってないような）トピックブランチならまだしも `filter-branch` で master 含む全体を書き換えた場合は、再度 `git clone` し直すなりブランチ消して `fetch` し直すよう他のメンバーにお願いします。

# GitHub のサポートに連絡する
これで GitHub 上で history や Pull Request のページからは該当ファイル・該当コミットにアクセスする道がなくなったので一見一段落に見えます。

…が、実はコミット単体のページは GitHub 上にキャッシュとして存在しており、https://github.com/dtan4/terraforming/commit/64f752dd8d93c5b7326175a69cece9e742fd010b のように__コミットハッシュを直接指定すると普通に閲覧できます__。

そこで、https://github.com/contact から GitHub のサポートにキャッシュを消すよう連絡する必要があります。勿論英語です。名前とメールアドレスは既に入っているので、

- __Subject:__ 

```
Request to remove page caches including sensitive data
```

- __Body:__

```
Hello,

We accidentally commited sensitive data into the repository.
According to https://help.github.com/articles/remove-sensitive-data/, we already ran `git filter-branch` on the branch and force-pushed.
Sensitive data was disappeared from git tree.

I'm sorry to trouble to you, but could you remove cached views of these commits from github.com?

*Target repository*

https://github.com/user/repo

*Commit hashes to be removed*

...
...
...
...

Thanks,
Daisuke
```

のように送ればよいです。
ここで記載するのはコミットハッシュだけで良いです。一応と思って Pull Request の URL も載せると __Pull Request も一緒に消されます__（実話）。

そうすれば、わりとすぐに（早ければ一時間かからないかも）「消しましたよ！」というメールが届きます。確認できれば、これで本当の一件落着です。お疲れ様でした :tada: 

# おわりに
GitHub 上からセンシティブなファイルを削除する方法を紹介しました。この記事のお世話にならないよう、日頃から__センシティブなファイルは `.gitignore` する__、__コミット前に diff を見る__ことを忘れないようにしましょう。

# REF
- [Remove sensitive data - User Documentation](https://help.github.com/articles/remove-sensitive-data/)
    - GitHub 公式 こういう状況に陥った場合の対処方法
- [GithubでPull Requestは絶対消せない。ヤバい＼(^o^)／ｵﾜﾀ - アジャイルSEの憂鬱](http://sinsoku.hatenablog.com/entry/2013/10/16/013106)
- [はてなブックマーク - GithubでPull Requestは絶対消せない。ヤバい＼(^o^)／ｵﾜﾀ - sinsokuのブログ](http://b.hatena.ne.jp/entry/sinsoku.hatenablog.com/entry/2013/10/16/013106)
    - サポートに連絡したらすぐ消してくれるとのこと
- [Git - 歴史の書き換え (filter-branch)](https://git-scm.com/book/ja/v1/Git-%E3%81%AE%E3%81%95%E3%81%BE%E3%81%96%E3%81%BE%E3%81%AA%E3%83%84%E3%83%BC%E3%83%AB-%E6%AD%B4%E5%8F%B2%E3%81%AE%E6%9B%B8%E3%81%8D%E6%8F%9B%E3%81%88#最強のオプション:-filter-branch)