ま、そんなところで。

ニッチな技術系メモとか、車輪を再発明してみたりとか.

Gitリポジトリを履歴を保ちつつLFSに移行する

GitHubリポジトリを移行したい!
となったとき、ファイルサイズ制限(100MB)やリポジトリサイズの警告(1GB)ですんなり行かないことがあります.
移行にはLFSの導入が必須となるのでリポジトリの再作成が必須なのですが、過去の履歴もなんとか保持したい・・ですよね.

履歴をそのままにして、過去に遡って巨大なファイルをLFS管理としてリポジトリを再構築する手順です.

1. 動機

2. 移行手順

基本的に git lfs migrate コマンドを使用して、リポジトリ内の大きなファイルをLFSに移行するのですが、履歴上のどのファイルを移行するかは今後の運用を考える上で重要です.
そのため、以下の手順で移行を行います.

Step1. 対象のファイルを確認: git lfs migrate info コマンドを使用して、巨大なオブジェクトのファイルを確認します.
Step2. 対象のファイルを選択: 移行に必要なmigrate import対象のファイルを選択し、必要なフィルタ設定を検討します.
Step3. 対象のファイルを移行: git lfs migrate import コマンドを使用して、対象のファイルをLFSに移行します.

2-1. 対象のリポジトリをクローンする

まず、対象のリポジトリをミラークローンして作業リポジトリにします.

mkdir <repository-name>.git
cd <repository-name>.git
git clone --mirror <repository-url> .

2-2. 対象のファイルを確認

リポジトリへ移動します.

cd <repository-name>.git

git lfs migrate info コマンドを使用して、リポジトリ内の大きなファイルを確認します.
どのファイルがどれだけサイズに影響を与えているかを確認します.

git lfs migrate info --everything
# 出力例:
Sorting commits: ..., done.                                                                                                  
Examining commits: 100% (8421/8421), done.                                                                                   
*.csv       1.0 GB    13388/13388 files 100%
*.lib       899 MB       76/76 files 100%
*.def       517 MB       95/95 files 100%
*.cs        494 MB   8432/8432 files 100%
*.dic       484 MB         3/3 files 100%

リポジトリ内のどのファイルが大きいのかを確認するには以下のコマンドを利用します.
上位から指定した件数のファイルを表示します.

# リポジトリ内のオブジェクトサイズ表示
git rev-list --objects --all | \
git cat-file --batch-check='%(objecttype) %(objectname) %(objectsize) %(rest)' | \
sort -k3 -n -r | head -n ${件数}

# サイズを単位付き表示とする場合はこちら
git rev-list --objects --all | \
git cat-file --batch-check='%(objecttype) %(objectname) %(objectsize) %(rest)' | \
sort -k3 -n -r | head -n ${件数} | \
awk '{print $1, $2, $3, $4}' | \
numfmt --field=3 --to=iec
# 出力例
blob 1234567890abcdef1234567890abcdef12345678 1000000000 path/to/largefile.zip
blob 1234567890abcdef1234567890abcdef12345679 900000000 path/to/anotherlargefile.zip

ちなみに、現在チェックアウトされているファイルで大きいものを探す場合は下記のコマンド

# .git 以下は除外する.
find . -type d -name '.git' -prune -o -type f -print0 | xargs -0 du -h | sort -hr | head -n ${件数}

2-2. 対象のファイルを選択

git lfs migrate import の include フィルタに適用するパラメータを検討します.
項目が多くなりがちなので、patterns.txt ファイルを作成して、摘要パターンを記載していきます.

*.psd                    # 特定の拡張子のファイル
hoge/fuga/*              # 特定のディレクトリ内直下の全てのファイル
hoge/fuga/*.zip          # 特定のディレクトリ内のzipファイル
hoge/fuga/**             # 特定のディレクトリ以下(サブディレクトリ含む)全てのファイル
hoge/fuga/**/hoge-*.zip  # 特定のディレクトリ以下(サブディレクトリ含む)の全てのhoge-*.zipファイル
/hoge/fuga/xxxx.zip      # 特定のパスのファイル

※ 実際のファイルにはコメントを含めないこと

2-3. 対象のファイルを移行

2-3-1. LFS移行の実行

patterns.txt ファイルを作成したら、以下のコマンドで対象のファイルをLFSに移行します.

git lfs migrate import --include="$(paste -sd , patterns.txt)" --everything

2-3-2. リポジトリの確認

移行後、リポジトリの状態を確認します.
指定したファイルがLFSで管理されていることを確認します.

git lfs ls-files

また、リポジトリのオブジェクト一覧から対象のファイルがLFS管理となって列挙されないことを確認します.

git rev-list --objects --all | \
git cat-file --batch-check='%(objecttype) %(objectname) %(objectsize) %(rest)' | \
sort -k3 -n -r | head -n ${件数}

2-4. リポジトリへpush

移行したリポジトリをリモートへpushします.
pushはmirrorを使用して、LFSオブジェクトも含めてpushします.

注意 : この操作はリモートリポジトリを上書きするため、慎重に行ってください.

git push --mirror origin

(参考)