ゆうなんとかさんの雑記帳的な。

Twitterで踊ったり音ゲーしたりしてるあの名前がよくわからない人が書いてるらしいよ。

不幸にもパスワードとかをリポジトリに含めてコミットしてしまったときの対処法をGitHubが教えてくれた

Remove sensitive data · GitHub Helpの翻訳です。直訳すると日本語として不自然になるようなところは一部意訳しています。

秘密情報の削除

パスワードや何かのキーをGitリポジトリにうっかりコミットしてしまうことは往々にしてあるものです。しかもそれらはgit rmコマンドでファイルを削除してもリポジトリの履歴に残り続けます。ですがご安心ください。gitにはとても簡単にリポジトリ全体の履歴からきれいさっぱりそのファイルを消し去る方法があります。
危険!: 一度コミットしてpushしてしまったそれらの情報は信用ならないものと考えるべきです。それがパスワードであれば絶対に変更しましょう。何かのキーであれば再生成するべきです。

ファイルをリポジトリから切り離す方法

パスワードを変更したら、そのファイルを履歴から消し去り、確実に再度コミットしてしまわないように.gitignoreに追加したいことでしょう。以下に示すのは、RakefileGitHub gemリポジトリから削除しようとする例です。

git clone https://github.com/defunkt/github-gem.git
# Initialized empty Git repository in /Users/tekkub/tmp/github-gem/.git/
# remote: Counting objects: 1301, done.
# remote: Compressing objects: 100% (769/769), done.
# remote: Total 1301 (delta 724), reused 910 (delta 522)
# Receiving objects: 100% (1301/1301), 164.39 KiB, done.
# Resolving deltas: 100% (724/724), done.

cd github-gem

git filter-branch --force --index-filter \
  'git rm --cached --ignore-unmatch Rakefile' \
  --prune-empty --tag-name-filter cat -- --all
# Rewrite 48dc599c80e20527ed902928085e7861e6b3cbe6 (266/266)
# Ref 'refs/heads/master' was rewritten

このコマンドは履歴全体のブランチやタグに対して実行され、Rakefileに関わったコミットと、それ以降のすべてのコミットを変更します。Rakefileのみを変更したコミットは変更なしと見なされ履歴から取り除かれます。削除したいファイルは名前だけでなく、特定するためのパスが必要であることに注意してください。

これで当該のファイルは履歴からきれいさっぱり消え去りました。それでは、もう二度とこのファイルをコミットしないようにしましょう。

これは既存のタグを上書きすることに気をつけてください。

echo "Rakefile" >> .gitignore

git add .gitignore

git commit -m "Add Rakefile to .gitignore"
# [master 051452f] Add Rakefile to .gitignore
#  1 files changed, 1 insertions(+), 0 deletions(-)

ここはいい機会なので、削除したかったファイルが履歴全体、そしてすべてのブランチからなくなっているかしっかり確認しておきましょう。この変更をリモートブランチに適用するためには--forceオプションをつけてpushする必要があります。この操作でリポジトリ全体を上書きしたため、あなたのコミットはオンラインでは有効でなくなったからです。

git push origin master --force
# Counting objects: 1074, done.
# Delta compression using 2 threads.
# Compressing objects: 100% (677/677), done.
# Writing objects: 100% (1058/1058), 148.85 KiB, done.
# Total 1058 (delta 590), reused 602 (delta 378)
# To https://github.com/defunkt/github-gem.git
#  + 48dc599...051452f master -> master (forced update)

このコマンドは変更されたブランチやタグごとに実行する必要があります。 --all や --tags というフラグをつけると作業がより楽になることでしょう。

既に移動してしまったファイルを切り離すには

作成してから移動してしまったファイルを切り離すには、移動する以前のパスでもfilter-branchを実行してください。

ワークスペースのクリーンアップ

git filter-branchで履歴を書き換えている間、ローカルリポジトリには参照が外れるかGCされるまでオブジェクトが残っています。しかし、メインリポジトリで作業しているときは強制的に切り離したいことでしょう。

rm -rf .git/refs/original/

git reflog expire --expire=now --all

git gc --prune=now
# Counting objects: 2437, done.
# Delta compression using up to 4 threads.
# Compressing objects: 100% (1378/1378), done.
# Writing objects: 100% (2437/2437), done.
# Total 2437 (delta 1461), reused 1802 (delta 1048)

git gc --aggressive --prune=now
# Counting objects: 2437, done.
# Delta compression using up to 4 threads.
# Compressing objects: 100% (2426/2426), done.
# Writing objects: 100% (2437/2437), done.
# Total 2437 (delta 1483), reused 0 (delta 0)

これは新しい、または空のGutHubリポジトリにpushしたあと、新たにそこからcloneするのと同じことです。

協業者とのやりとり

汚染されたブランチをpullし、新たに自身でブランチを作ってしまった協業者がいることでしょう。あなたの新しいブランチをfetchしてもらったら、git rebaseを使って彼らのブランチを新しいブランチの上にrebaseしてもらう必要があります。また、.gitignoreをオーバーライドして当該のファイルを再び取り込んでしまわないようにしてもらいましょう。そのためにも、協業者には必ずmergeではなくrebaseをしてもらいましょう。さもなくば、彼らは汚染された履歴やファイルを再び取り込んでしまい、そこで高い確率でマージの競合が起きてしまいます。

GitHubのキャッシュデータ

--forceオプションをつけたpushは新しいコミットの変更を取り入れ、ブランチのポインタをそこに移動するだけです。リモートリポジトリからコミットを削除しないことに気をつけてください。SHA1ハッシュを使って直接問題のコミットにアクセスされないようにするためには、リポジトリを削除して作り直さなければなりません。オンラインで閲覧されたコミットもキャッシュされます。リポジトリを作り直したあとにキャッシュされたページを確認したらスタッフがキャッシュから切り離すことができるように、GitHubサポートでチケットを作成してリンクを紹介してください。

事故的コミットを繰り返さないために

最後に、望まないコミットを避けるための簡単なやり方をご紹介します。まずいちばん簡単なのは、GitHub for MacgitxといったGUIプログラムでコミットすることです。これは何をコミットしようとしていて、リポジトリに追加したいものだけを追加しているかを確実に見ることができます。コマンドラインから作業するときは、全体を対象にするするような git add .やgit commit -a といったコマンドを使うことを避け、代わりに git add filename や git rm filename といったコマンドでファイルを個別にステージングすることをお勧めします。また、git add --interactive を使って、ファイルを確認しながら、全体か一部をステージングするかどうかを決めることができます。さらに、git diff --cachedコマンドを使うと、コミットするためにステージングした変更を見ることができます。これは-aオプションなしでgit commitしたときにコミットされる正確な差分です。