GitHubでやらかしたコミット履歴を(ほぼ)見えなくする方法

Gitを覚えたての初心者が一番よくやらかすミスは、パスワードやAPIキーをハードコーディングしたものをリモートリポジトリにプッシュしてしまうことじゃないだろうか。
もちろん速やかにパスワードやキーの変更をすることは前提だが、問題は恥ずかしいコミット履歴がその後も残ってしまうことだ。
そんなときにどうすればいいか、この前teratailで他の人の回答を見る機会があったので実際に試してみた。

はじめに

この方法は、ざっくり言えば歴史を改変することに等しい。多人数で開発をしているときに何の断りもなく使うと、トラブルが起こる可能性が高いので注意すること。

やらかしたコミットを作る

実際にリポジトリを作り、プッシュを2回行った。内容はteratailのAPIを叩くサンプルコードだ。
github.com

$ git log --oneline 
bd6c846 (HEAD -> master, origin/master) アクセストークンを追加
b7ee88e initial commit

2回目でうっかりアクセストークンが見える状態のコードをコミットし、気づかずにそのままプッシュしてしまった。この履歴を消したい。

git rebaseでコミットを消す

git rebaseは、過去のコミットを編集してコミット履歴を整理するためのコマンドである。
本来の使い方ではないが、これを使ってやらかしたコミットを履歴から消すことができる。

git rebase -i b7ee88eのように、消したいコミットのひとつ前のコミット識別番号を指定する。

pick b41300e アクセストークンを追加 

# Rebase b7ee88e..b41300e onto b7ee88e (1 command)
#
# Commands:
# p, pick = use commit
# r, reword = use commit, but edit the commit message
# e, edit = use commit, but stop for amending
# s, squash = use commit, but meld into previous commit
# f, fixup = like "squash", but discard this commit's log message
# x, exec = run command (the rest of the line) using shell
# d, drop = remove commit
#
# These lines can be re-ordered; they are executed from top to bottom.
#
# If you remove a line here THAT COMMIT WILL BE LOST.
#
# However, if you remove everything, the rebase will be aborted.
#
# Note that empty commits are commented out

エディタが開いたら、pickの部分を変えることで様々な編集ができる。今回はdropに変更して保存することで、そのコミットを消せる。

$ git log --oneline
b7ee88e (HEAD -> master) initial commit

やらかしたコミットが消えた。
あとはプッシュするだけ。プッシュする際は、普通にgit push origin masterとするとrejectされるので、git push --force origin masterのようにする。
今回使ったリポジトリを確認してほしい。initial commitしか見えないはずだ。

【参考】厳密には消えてない

ただしGitは歴史を全て保存するので、厳密には消えたわけではない。
git reflogコマンドを叩くと、git logでは出てこないものも含めた全ての履歴が表示される。git rebaseで編集した跡も残っている。

$ git reflog
b7ee88e (HEAD -> master, origin/master) HEAD@{0}: rebase -i (finish): returning to refs/heads/master
b7ee88e (HEAD -> master, origin/master) HEAD@{1}: rebase -i (start): checkout b7ee88e
b41300e HEAD@{2}: rebase -i (finish): returning to refs/heads/master
b41300e HEAD@{3}: rebase -i (pick): アクセストークンを追加
27ce491 HEAD@{4}: rebase -i (pick): initial commit
9d36eb3 HEAD@{5}: rebase -i (pick): initial commit
17ff75b HEAD@{6}: rebase -i (start): checkout 17ff75b6f56acea009e986d19a152298efb7dabb
bd6c846 HEAD@{7}: rebase -i (finish): returning to refs/heads/master
bd6c846 HEAD@{8}: rebase -i (start): checkout HEAD~1
bd6c846 HEAD@{9}: rebase -i (finish): returning to refs/heads/master
bd6c846 HEAD@{10}: rebase -i (start): checkout b7ee88e
bd6c846 HEAD@{11}: commit: アクセストークンを追加
b7ee88e (HEAD -> master, origin/master) HEAD@{12}: commit (initial): initial commit

コミット識別番号がわかれば、そのコミットが見える。
GitHub上でも、以下のURLを叩くと今回やらかしたコミットが見えてしまう。
https://github.com/Udomomo/changeHistory/commit/bd6c846

もっとも、コミット識別番号がわからない限り見ることができないため、赤の他人に漏れる心配はほとんどない。
GitHub社員はユーザのリポジトリのreflogを見られるようだが、それを悪用したら当然サービスとしての信頼が失墜するので、基本的には考慮しなくてよいだろう。

注意点

  • initial commitでやらかしてしまった場合、通常のrebaseでは対応できない。
    このサイトによれば、git rebase -i --rootとしなければいけないようだ。

d.hatena.ne.jp

  • GitHubでは、やらかしたコミットを使ってプルリクエストを投げてしまうと、それを消すことはできない。
    そもそもプルリクエストを完全に削除する方法が存在しないらしい。

  • 最後になるが、多人数での開発時はむやみにrebaseを使わないこと。やらかしたらまずパスワードやキーを変えること。
    そして一番大事なのは、大事な情報をハードコードしない習慣をつけること。いいですね?