やりたいこと
リポジトリの一部のディレクトリを、コミット履歴ごと別のリポジトリに切り出したいケースがあります。
例えば、以下のようなリポジトリ構成で、aaa ディレクトリを独立したリポジトリに分離する場合です。
old リポジトリ(移行元)
├── aaa/ ← これを別リポジトリとして切り出したい
├── bbb/
└── README.md
切り出し後は、aaa ディレクトリの中身がルートになる新しいリポジトリ new ができます。
aaa 内のファイルに関するコミット履歴もそのまま引き継がれます。
手順
前提条件
- GitHub 上に新しい空のリポジトリ
newを作成済み - 古いリポジトリのバックアップ (
git clone) を取得済み
この手順では git filter-branch を使って古いリポジトリの履歴を書き換えるため、作業前に必ず古いリポジトリのバックアップを取ってください。
ステップ 1: 古いリポジトリをミラークローンする
$ git clone --mirror https://github.com/maku77/old.git
$ cd old.git
--mirror を付けることで、すべてのブランチ・タグを含む完全なコピーを取得します(ただし、作業ディレクトリの存在しないベアリポジトリとなります)。
ステップ 2: 特定ディレクトリをルートに書き換える
$ git filter-branch -f --subdirectory-filter aaa -- --all
--subdirectory-filter aaa は、aaa ディレクトリ内のファイルだけを残し、それをリポジトリのルートに移動させるオプションです。
aaa 以外のファイルに関するコミット履歴は自動的に削除されます。
末尾の -- --all は、すべてのブランチに対してこの操作を適用することを意味します。
ステップ 3: 新しいリポジトリとして push する
リモートの向き先を新しいリポジトリに変更して push します。
$ git remote remove origin
$ git remote add origin https://github.com/maku77/new.git
$ git push --all # すべてのブランチを push
$ git push --tags # すべてのタグを push
ステップ 4: 新しいリポジトリを確認する
$ git clone https://github.com/maku77/new.git
$ cd new
$ git log --oneline # コミット履歴が引き継がれていることを確認
古いリポジトリから対象ディレクトリを削除する(任意)
移行が完了した後、古いリポジトリ側から aaa ディレクトリとその履歴を完全に削除したい場合は、以下のようにします。
$ git clone https://github.com/maku77/old.git
$ cd old
$ git filter-branch --force --index-filter "git rm -rf --ignore-unmatch aaa/" --prune-empty -- --all
$ git push --force
--index-filterは、各コミットのインデックス(ステージングエリア)を書き換えるオプションです。--prune-emptyは、ファイルの変更がなくなった空のコミットを自動的に削除します。--forceを付けて push することで、書き換えた履歴をリモートに反映します。
.git-rewrite': Directory not empty というエラーが出た場合は、前回の filter-branch の作業ファイルが残っています。
もう一度同じコマンドを実行すると成功することがあります。