ブランチと手術

履歴からファイルを削除する

git rm が止められるのは、ファイルが 新しい コミットに現れることだけです。すでに 作られたコミットには何もしません。blob はオブジェクトデータベースの中に残り、あらゆ るクローンの中に残り、git show 一発の距離にあり続けます。

これが問題になる場面は 2 つあります。そのファイルが認証情報だったときと、400 MB だったときです。

⌘K → 履歴からファイルを削除、またはファイルを右クリック — プロジェクトツリー でも、コミットのファイル一覧でも、コミットコンポーザーでも構いません。ファイルを 削除した コミットこそ、それがまだ履歴に残っていると人が気づく場所なので、出口も そのメニューの中にあります。

パスを見つける

入り口は 2 つあり、それぞれ違う問いに答えます。

入力する — リポジトリからの相対パスで、先頭のスラッシュなし — 何を消しに来たか すでにわかっているときはこれです。

履歴を見る のは、わかっていないときです。これまでにコミットされたすべてのパスを、 重いものから順に、それぞれ何個のバージョンを持っているか、いまも追跡されているかと 一緒に並べます。削除済みのパスにはその印が付き、たいていそれが探しているものです。 作業ツリーからは消えているのにあらゆるクローンには残っているファイルは、通常の ファイルダイアログでは見せようがない場合そのものだからです — 選ぼうにも、そこに ファイルがないのですから。

同じ一覧は、人がここへ来るもう 1 つの理由 — なぜこのクローンは 2 ギガバイトある のか — にも答えます。各パスの blob が実際に占めているバイト数で並んでいるからです。 行を選べば、その場で計測されます。

これまでにコミットされたすべてのパスを重いものから順に並べ、削除済みには印を付けた一覧

同意する前に計測する

計測 を押します(または行を選びます)。この時点ではまだ何も書かれません。得られる のは次のものです。

書き換えるコミット そのファイルを最初に含んだコミット以降のすべて
ブランチ / タグ 動くことになる ref
blob が占めている容量 それらのバージョンが実際に占めているバイト数
最初のコミット 書き換えの起点 — それ以降はすべて新しいハッシュになる

計測結果。書き換えるコミット、影響を受ける ref、占有バイト数、そしてどのみち秘密情報は失効させよという警告

数がゼロなら、パスが間違っています。それはたいてい綴りかディレクトリの接頭辞の問題 であって、存在しないという意味ではありません。

書き換えが実際にやること

Gitcito はすべてのブランチとタグを refs/gitcito/pre-purge/<timestamp>/… にコピー してから、次を実行します。

git filter-branch --force \
  --index-filter 'git rm --cached --ignore-unmatch -- <path>' \
  --prune-empty --tag-name-filter cat -- --branches --tags

--index-filter はコミットを 1 つずつチェックアウトするのではなくインデックスを直接 書き換えます。これが数分と数時間の差になります。--all ではなく --branches --tags にしてあるのは意図的です。--all はバックアップの ref も含んで しまい、書き換えが自分の安全網を食べてしまうからです。

削除したファイル以外に何も含んでいなかったコミットは落とされます(--prune-empty)。 タグは書き換え後のコミットに貼り直されます。

バックアップと、容量がまだ戻らない理由

この消去は取り消せます。その代償が、あなたがそう言うまでディスク容量は回収され ない ということです。バックアップが存在するあいだ古いコミットには到達できるので、 git はそれらを回収しません。

操作 効果
復元 すべてのブランチとタグが消去前のコミットに戻り、ファイルも一緒に戻ってくる
バックアップを破棄 バックアップの ref を削除し、reflog を期限切れにし、git gc --prune=now を実行 — 容量が戻り、消去はここで永久になる

1 段階ではなく 2 段階なのは、前半が取り戻せる側で、後半がそうではないからです。

どのみち秘密情報は失効させる

認証情報が一度でもプッシュされたのなら、履歴を書き換えても漏洩は取り消せません。 誰かがすでにフェッチしているかもしれませんし、フォージのサーバーは参照されなくなった オブジェクトを抱えたままですし、CI のログがそれを出力していたかもしれません。書き換 えが止められるのはこれ以上広がることであって、すでに起きた露出を巻き戻すことでは ありません。

鍵を失効させて作り直してください。そのうえで消去し、次にクローンする人がそれを見つ けないようにします。

やってくれないこと

  • プッシュはしません。 書き換えはローカルの話です。結果を公開するということは、 影響を受けるすべてのブランチへの強制プッシュを意味し、他の全員が再クローンするか ハードリセットしなければならなくなります — その判断が置かれている場所が 強制プッシュのガード です。
  • 作業ツリーが汚れているとき、あるいはマージ/リベースの途中では拒否します。書き 換えは HEAD を何度も動かすもので、コミットしていない作業のまわりでそれをやるのが、 作業を失う経路そのものだからです。
  • 書き換えの単位はパスであって、内容ではありません。 独立したファイルではなく ソースファイルに貼り付けられてしまった秘密情報を消すには内容フィルターが必要で、 それは git filter-repo --replace-text の領分であり、Gitcito はそれを包んでいま せん。
  • filter-branch は非常に大きな履歴では遅いです。 それでも、どこでも git に付属 してくるのがこれであり、だから Gitcito が使うのもこれです。数万コミットあるリポジ トリでは、ターミナル で git filter-repo を使うほうが速い道具です。
  • 他人のクローンはあなたのリポジトリではありません。 彼らは再クローンするまで 古い履歴を持ち続けます。

この翻訳を GitHub で改善