Git リポジトリのメンテナンス

Git リポジトリのメンテナンスには一般的に、リポジトリ サイズの縮小が含まれます。他のバージョン管理システムからインポートした場合、インポート後に不要なファイルをクリーンアップする必要がある場合があります。ここでは、Git リポジトリからの大きなファイルの削除について、および、以下のトピックについて説明します。


注意

このページの手順は、破壊的な操作を含む高度なテクニックを使用しています。内容を注意深く確認し、開始前にリポジトリのバックアップを行うようにしてください。バックアップを作成する最も簡単な方法は、--mirror フラグを使用してリポジトリをクローンし、クローン全体を圧縮するやり方です。バックアップがあれば、メンテナンス中にリポジトリの主要なエレメントを誤って破損してしまっても、回復することができます。

メンテナンスはリポジトリ ユーザーにとって破壊的になる可能性があることに注意する必要があります。リポジトリをメンテナンスすることについて、ワークスペースのメンバーまたはリポジトリのフォロワーに伝えることをおすすめします。全員がコードをチェックイン済みで、メンテナンス中の開発の一時停止に合意していることを確認してください。

ローカルでサイズを小さくした場合でも、このページで説明されている方法で Git の履歴からファイルを削除しても、Bitbucket Cloud で報告されるリポジトリのサイズが小さくならない場合があることに注意することも重要です。これは、マージされたプル リクエストの差分を保存するために、クローンでは表示されない Git コミットへの追加の参照が Bitbucket Cloud によって保存されるためです。これらの参照を特定して Bitbucket Cloud ストレージから削除するには、当社のサポート チームまでご連絡いただく必要があります。参照を削除すると、過去のプル リクエスト データが削除される可能性があることに注意してください。


Git リポジトリからのファイル削除について理解する

リポジトリをクローンすると、全ソースコード ファイルのすべてのバージョンを含め、履歴全体のクローンが作成されます。ユーザーが JAR など大きいファイルをコミットすると、後から作成するすべてのクローンにこのファイルが含まれます。後のコミットでプロジェクトからこのファイルを削除したとしても、ファイルは依然としてリポジトリ履歴内に存在します。このファイルをリポジトリから削除するには、次の作業が必要です。

  • プロジェクトの現在のファイルツリーからファイルを削除する

  • リポジトリ履歴からファイルを削除する - Git 履歴を書き換え、履歴を含むすべてのコミットをファイルから削除する

  • 古いコミット履歴を参照する reflog 履歴をすべて削除する

  • リポジトリをリパックし、git gc を使用して、現在は使用していないデータをガーベジコレクトする

Git 「GC」(ガベージ コレクション)は、どのブランチやタグも実際に使用せず、何らの参照もしていないすべてのデータをリポジトリから削除します。ガベージ コレクションを有効にするには、不要ファイルを含むリポジトリ履歴をすべて書き換えて、不要ファイルを参照しないようにする必要があります。これで git gc は現在使用されていないデータを破棄できるようになります。

リポジトリの履歴書き換えは慎重を要する作業です。というのは、すべてのコミットがその親に依存し、どんな小さな変更であっても、以降のすべてのコミットのコミット ID を変更するためです。この作業用に 2 つの自動化ツールが利用できます。

  1. BFG Repo Cleaner - 処理が速く、シンプルで使いやすい。Java 6 以降が必要です。

  2. git filter-branch - 強力ではあるが、設定が難しく、規模の大きいリポジトリの場合、処理が遅い。 コア Git スイートの一部です。

BFG または filter-branch のいずれを使用した場合も、履歴を書き換えたあとに古い履歴をポイントする reflog エントリを削除し、ガベージ コレクタを実行して古いデータを削除する必要があります。 

BFG による履歴の書き換え

BFG は大規模ファイルやパスワードなど、不要なデータを Git リポジトリから削除することを目的とする特別のツールで、(現在のコミットに含まれない)サイズの大きい履歴を簡単に削除する「--strip-blobs-bigger-than」フラグを備えています。

1 $ java -jar bfg.jar --strip-blobs-bigger-than 100M

100 MB 以上のサイズのファイル (BFG により最新のコンテンツが保護されているため、最新のコミットに含まれないファイルのみ) は Git リポジトリの履歴から削除されます。必要に応じ、ファイル名による指定も可能です。

1 $ java -jar bfg.jar --delete-files *.mp4

BFG は git filter-branch よりも 10 ~ 1000 倍高速で、一般にはるかに使いやすいツールです。詳細については、使用方法使用例を確認してください。

git filter-branch による履歴書き換え(代替方法)

filter-branch コマンドは BFG と同様に Git リポジトリの変更履歴を書き換えますが、処理速度は遅く、手作業も増えます。大規模なファイルの場所がわからない場合、まずそれを見つける必要があります。

手作業によってリポジトリ内の大規模ファイルを確認する

Antony Stubbs はこの作業を効率的に実行する BASH スクリプトを書きました。 このスクリプトはパックファイルの内容を調べ、大規模ファイルを一覧表示します。ファイルの削除を開始する前に、次の操作を行い、このスクリプトを入手してインストールします。

  1. ローカル システムにスクリプトをダウンロードします

  2. Git リポジトリにアクセス可能な、既知の場所にスクリプトを置きます。

  3. スクリプトを実行可能にします。

    1 $ chmod 777 git_find_big.sh
  4. ローカルシステムにリポジトリのクローンを作成します。

  5. リポジトリのルート ディレクトリに移動します。

  6. Git ガベージコレクタを手動で実行します。

    1 git gc --auto
  7. .git フォルダーのサイズを調べます。

    1 2 $ du -hs .git/objects 45M .git/objects 

    あとで参照するために、このサイズを書きとめます。

  8. git_find_big.sh スクリプトを実行して、リポジトリ内の大規模なファイルを一覧表示します。

    1 2 3 4 5 6 7 8 $ git_find_big.sh All sizes are in kB's. The pack column is the size of the object, compressed, inside the pack file. size pack SHA location 592 580 e3117f48bc305dd1f5ae0df3419a0ce2d9617336 media/img/emojis.jar 550 169 b594a7f59ba7ba9daebb20447a87ea4357874f43 media/js/aui/aui-dependencies.jar 518 514 22f7f9a84905aaec019dae9ea1279a9450277130 media/images/screenshots/issue-tracker-wiki.jar 337 92 1fd8ac97c9fecf74ba6246eacef8288e89b4bff5 media/js/lib/bundle.js 240 239 e0c26d9959bd583e5ef32b6206fc8abe5fea8624 media/img/featuretour/heroshot.png

    すべての大規模なファイルが JAR ファイルです。pack サイズ欄が最も重要です。aui-dependencies.jar は 169 KB  に圧縮されていますが、emojis.jar は 580 KB にしか圧縮されていません。emojis.jar が削除の候補です。

filter-branch を実行する

このコマンドに、Git インデックス書き換え用のフィルターを渡すことができます。たとえば、あるフィルターはすべてのインデックス済みコミットからファイルを削除します。このコマンドの構文は次のとおりです。

git filter-branch --index-filter 'git rm --cached --ignore-unmatch pathname' commitHASH

--index-filter オプションは、リポジトリのステージング (またはインデックス) を変更します。--cached オプションは、ディスクではなくインデックスからファイルを削除します。フィルターを実行する前に各リビジョンをチェックアウトする必要がないため、この方が速くなります。git rm の -ignore-unmatch オプションは、削除しようとしている pathname が存在しない場合にコマンドが失敗するのを防ぎます。commit HASH を指定することにより、この HASH 値以上のあらゆるコミットから pathname を削除します。最初から削除するには、この指定をオフにするか、HEAD を指定します。  

大規模ファイルが別々のブランチにある場合は、ファイルごとに名前を指定して削除する必要があります。すべてのファイルが 1 つのブランチ内にある場合は、ブランチ自体を削除します。

オプション 1: 名前を指定してファイルを削除する

以下の手順により、大規模ファイルを削除します。

  1. 次のコマンドを実行し、特定した最初の大規模ファイルを削除します。

    1 git filter-branch --index-filter 'git rm --cached --ignore-unmatch filename' HEAD
  2. 残りの大規模ファイルごとに、手順 1 を繰り返します。

  3. リポジトリ内の参照を更新します。filter-branch は、refs/original/ 以下に名前空間を指定して、オリジナル参照のバックアップを作成します。正しくファイルが削除されたことを確認できたら、次のコマンドを実行して、バックアップされた参照を削除することにより、大規模なオブジェクトのガベージ コレクトを行わせることができます。

    1 $ git for-each-ref --format="%(refname)" refs/original/ | xargs -n 1 git update-ref -d

オプション 2: ブランチのみを削除する

すべての大規模ファイルが 1 つのブランチ内にある場合は、ブランチを削除するだけですみます。ブランチの削除により、すべての参照が自動的に削除されます。

  1. ブランチを削除します。

    1 $ git branch -D PROJ567bugfix
  2. 削除したブランチからの無効な reflog 参照のすべてを削除します。

    1 $ git reflog expire --expire=now PROJ567bugfix

不要データのガベージ コレクト

  1. (1 つのブランチのみを明示して作業している場合を除き)現在削除ずみのデータからの無効な reflog 参照をすべて削除します。

    1 $ git reflog expire --expire=now --all
  2. ガベージコレクタを実行し、古いオブジェクトを削除して、リポジトリをリパックします。

    1 $ git gc --prune=now
  3. すべての変更を Bitbucket リポジトリにプッシュバックします。

    1 $ git push --all --force
  4. タグもすべてカレントであることを確認します。

    1 $ git push --tags --force

さらにヘルプが必要ですか?

アトラシアン コミュニティをご利用ください。