社内のサーバーにホスティングしていた巨大な Git リポジトリを GitHub に移行する機会があったので、そのときにやったことを備忘録代わりとして書きます。

リポジトリの概要

ちょっと、巨大というと誇張しすぎですが、対象のリポジトリは以下のようになっています。

  • Unity プロジェクト
  • コミットの数は、9,000 弱
  • リポジトリのサイズは 5 GB 程度
  • プロジェクトの期間は 3 年ほど
  • 所々 で 100 MB 超えのファイルがコミットされている
  • 一部のアセットは既に GitHub のリポジトリとなっていて、 LFS で管理されている

要望

  • このリポジトリを、ひとつの GitHub リポジトリとして統合したい
  • また、そのときにアセット類は LFS で管理したい

この要望に対応するため、スクリプトを書いてゴニョゴニョします。

やるべきことは、以下の 2 点です。

  1. 社内サーバーにホスティングされている Git リポジトリを GitHub に移行する
  2. アセット類のリポジトリ統合する

社内サーバーにホスティングされている Git リポジトリを GitHub に移行する

通常であれば、社内サーバーのリポジトリをローカルにクローンして、リモートリポジトリとして GitHub を追加し、 Push で OK なのですが、100 MB 超えのファイルのコミットがあると GitHub が受け付けてくれず、 Push が失敗してしまいます。

そこで、伝家の宝刀 git filter-branch コマンドを使ってリポジトリの履歴を書き換えてしまいます。

すべてのブランチをチェックアウトする

masterdevelop ブランチのみ、であれば単純なのですが、 master-XXX など、大量のブランチが残っていて、どのブランチが必要で、どのブランチが不要なのかが分からない。という状況だったので、とにかく全てのブランチをチェックアウトすることにしました。

以下のようなスクリプトを書いて実行します。

#!/bin/bash

branches=$(git branch -r --no-merged | grep -v HEAD | sed -e "s/origin\///")

for branch in ${branches}; do
    echo ${branch}

    git checkout -b ${branch} origin/${branch}
done

過去のコミットに Git LFS を対応させる

過去のコミットのバイナリファイルなどを LFS で管理するために git filter-branch で履歴を書き換えていきます。
LFS 化するファイルの拡張子は予め決めておきます。

#!/bin/bash

# LFS 化するファイル拡張子を列挙しておく
extensions='
png
jpg
mp3
'

# 列挙した拡張子からコマンドを作成する
tree_filter_command="git lfs track "
for extension in ${extensions}; do
    tree_filter_command=${tree_filter_command}\"*.${extension}\"" "
done

# git filter-branch を適用する
git filter-branch \
    --tree-filter "${tree_filter_command} >/dev/null" \
    --index-filter "rm .gitattributes" \
    --tag-name-filter cat -- \
    --all

このスクリプトを実行して、全てのブランチを書き換えていきます。
この処理に一晩くらいかかりました。

あとは、通常どおり、リモートブランチを切り替えて、プッシュすれば移行は完了です。

リモートブランチを切り替える

$ git remote remove origin
$ git remote add origin git@github.com:foo/bar.git

プッシュ

$ git push -u origin master
$ git push --all origin

掃除

最後にローカルリポジトリを掃除します。

$ rm -rf .git/refs/original/
$ git reflog expire --expire=now --all
$ git gc --aggressive --prune=now

アセット類のリポジトリを統合する

まずは、リソースのリモートリポジトリを登録します。

$ git remote add resource git@github.com:foo/bar-resource.git

リソースの master ブランチをチェックアウト(名前は本流のブランチとは別にします)

$ git fetch resource
$ git checkout -b feature/resource-master resource/master

リソースのブランチを本流のブランチにマージします。
この時、 --allow-unrelated-histories を付けることで別のリポジトリを統合することができます。
いきなり master にマージするのは怖いので feature/merge-resource というマージ用のブランチを用意して作業しました。

$ git checkout master
$ git checkout -b feature/merge-resource
$ git merge --allow-unrelated-histories feature/resource-master

最後にリソースディレクトリの移動などを行って完了です。

まとめ

  • 履歴の書き換えには、 git filter-branch を使う
  • 別のリポジトリを統合するには、 --allow-unrelated-histories を使う

Git はローカルで作業して失敗してもリモートには影響がないので、こういった作業も比較的気軽に行えて便利ですね。