🐙

特定のファイル・ディレクトリのコミット履歴を保持したまま別のリポジトリに移動させる方法

2024/01/27に公開

読むのがめんどくさい人向け

shell
# コピー元リポジトリから対象ファイル・ディレクトリの全履歴を取得
cd <コピー元リポジトリ>
git format-patch --root -o <パッチファイルの出力先> <対象ファイル・ディレクトリ>

# パスの修正 (必要に応じて)
cd <パッチファイルの出力先>
find . -type f -exec sed -i 's/<変換前のパス>/<変換後のパス>/g' {} +

# コピー先リポジトリに対象ファイル・ディレクトリをコピー
cd <コピー先リポジトリ>
git am <パッチファイルの出力先>/*

# お片付け
rm -rf <パッチファイルの出力先>

はじめに

今までこのファイルやこのディレクトリはリポジトリ A で管理していたけど、やっぱりリポジトリ B で管理したい! っていうこと、ありませんか?

単にファイルやディレクトリを移動・コピーするだけなら mv コマンドや cp コマンドで簡単にできますが、せっかく Git で管理しているのにこれだと履歴が途絶えてしまいます。

今回はファイルやディレクトリに基づくコミット履歴を保持しつつコピーする方法について紹介します。また、コピー先でパスを改変する方法についても併せて解説します。

想定ケース

説明を分かりやすくするため、ホームディレクトリを作業スペースとします。

  • Git の履歴を保持したまま ~/proj_a/dir_a/want_to_move.txt を別リポジトリ ~/proj_b/ にコピーする
  • コピー後は ~/proj_b/dir_a/want_to_move.txt ではなく ~/proj_b/want_to_move.txt として保存する (Git の履歴からパスを改ざんする)
  • パッチファイル (Git の履歴情報が含まれるファイル) は一時的に ~/tmp に保存する

事前準備

「想定ケース」に挙げたディレクトリ構造を用意します。ディレクトリ構造が理解できている方は読み飛ばしても構いません。

ディレクトリ ~/proj_a (と ~/proj_a/dir_a), ~/proj_b, ~/tmp を作ります。

shell
mkdir -p ~/proj_a/dir_a
mkdir ~/proj_b
mkdir ~/tmp

~/proj_a, ~/proj_b は Git ディレクトリなので Git の準備を行います。

shell
cd ~/proj_a
git init

cd ~/proj_b
git init

~/proj_a/dir_a/want_to_move.txt を作り、適当にファイルの中身を編集してコミットします。

shell
cd ~/proj_a
touch dir_a/want_to_move.txt

git add dir_a/want_to_move.txt
git commit -m "foo"

echo hello > dir_a/want_to_move.txt
git add dir_a/want_to_move.txt
git commit -m "bar"

echo world >> dir_a/want_to_move.txt
git add dir_a/want_to_move.txt
git commit -m "baz"

~/proj_a には、~/proj_a/dir_a/want_to_move.txt に何かしらの変更を加えたコミットが 3 つ生成されているはずです。

shell
cd ~/proj_a
git log
commit c1a2f935cdae186c877858e0f618daa864061db2
Author: user <user@example.com>
Date:   Sat Jan 27 02:45:00 2024 +0900

    baz

commit ea2783d901c4f8539579507d3a0a7010f9abf35e
Author: user <user@example.com>
Date:   Sun Jan 21 01:20:59 2024 +0900

    bar

commit d3367c44334d7a3d39f455bd04b2d363c59ab282
Author: user <user@example.com>
Date:   Wed Jan 17 07:40:42 2024 +0900

    foo

これで「想定ケース」に挙げたディレクトリ構造が完成しました。

実現方法

~/proj_a/dir_a/want_to_move.txt~/proj_b/ にコピーしたいとします。ただ、cp コマンドでコピーすると Git の履歴まではコピーできません。

Git の履歴を保持したまま別のリポジトリにファイルをコピーするには、以下の操作を行います。

  1. コピーしたいファイルに関わる全コミットをパッチファイルとしてダンプする
  2. コピー先のディレクトリ (リポジトリ) でパッチファイルを適用する

作成されるパッチファイルはどこに置いても良いのですが、~/proj_a~/proj_b に置くとややこしいので ~/tmp に置くことにします。

操作手順

具体的な操作手順は以下のとおりです。

まず、パッチファイルを作成します。

shell
cd ~/proj_a
git format-patch --root -o ~/tmp dir_a/want_to_move.txt

~/tmp にパッチファイルが作成されます。want_to_move.txt を編集するコミットを 3 つ作ったので 3 つのファイルが生成されているはずです。

shell
ls ~/tmp
0001-foo.patch
0002-bar.patch
0003-baz.patch

foo, bar, baz の部分はコミットメッセージになります。

~/proj_a 内では dir_a/want_to_move.txt というパスになっているので、このままパッチファイルを適用すると ~/proj_b/dir_a/want_to_move.txt となります。

そうではなく ~/proj_b/want_to_move.txt としたい場合は以下のようにパッチファイルの中身を改ざんする必要があります。

shell
cd ~/tmp
find . -type f -exec sed -i 's/dir_a\/want_to_move.txt/want_to_move.txt/g' {} +

同じパスで良い場合は上記のコマンドはスキップしてください。

最後に ~/proj_b でパッチファイルを適用します。

shell
cd ~/proj_b
git am ~/tmp/*

これで want_to_move.txt の履歴を保持したまま ~/proj_b/want_to_move.txt としてファイルをコピーすることができました。

shell
cd ~/proj_b
git log
commit c1a2f935cdae186c877858e0f618daa864061db2
Author: user <user@example.com>
Date:   Sat Jan 27 02:45:00 2024 +0900

    baz

commit ea2783d901c4f8539579507d3a0a7010f9abf35e
Author: user <user@example.com>
Date:   Sun Jan 21 01:20:59 2024 +0900

    bar

commit d3367c44334d7a3d39f455bd04b2d363c59ab282
Author: user <user@example.com>
Date:   Wed Jan 17 07:40:42 2024 +0900

    foo

お片付け

作業が完了したら ~/tmp は削除しても大丈夫です。

shell
rm -rf ~/tmp

一般的な説明

上記ではファイルをコピーすることを例に挙げましたが、実際にはディレクトリを丸ごとコピーすることもできます。その場合はもちろんそのディレクトリ内にあるファイル・ディレクトリに関わるすべてのコミット履歴を保持するパッチファイルが作成されます。

特定のファイル・ディレクトリに関わるすべてのコミット履歴をパッチファイルとして特定のディレクトリ内に出力するには以下のコマンドを実行します。

shell
cd <コピー元リポジトリ>
git format-patch --root -o <パッチファイルの出力先> <対象ファイル・ディレクトリ>

コピー先リポジトリでパスを改変したい場合は以下のコマンドを実行します。

shell
cd <パッチファイルの出力先>
find . -type f -exec sed -i 's/<変換前のパス>/<変換後のパス>/g' {} +

ディレクトリ構造を表す /\/ に変換する必要があります。たとえば dir_a/want_to_move.txtdir_a\/want_to_move.txt とする必要があります。

パッチファイルを適用するには以下のコマンドを実行します。

shell
cd <コピー先リポジトリ>
git am <パッチファイルの出力先>/*

参考

GitHubで編集を提案

Discussion