From c7a9b470a620b531038ce0ad68e8502f8a0c3f99 Mon Sep 17 00:00:00 2001 From: Kosuke Aoki Date: Sat, 27 Jan 2024 13:37:18 +0900 Subject: [PATCH] =?UTF-8?q?=E3=80=8C=E7=89=B9=E5=AE=9A=E3=81=AE=E3=83=95?= =?UTF-8?q?=E3=82=A1=E3=82=A4=E3=83=AB=E3=83=BB=E3=83=87=E3=82=A3=E3=83=AC?= =?UTF-8?q?=E3=82=AF=E3=83=88=E3=83=AA=E3=81=AE=E3=82=B3=E3=83=9F=E3=83=83?= =?UTF-8?q?=E3=83=88=E5=B1=A5=E6=AD=B4=E3=82=92=E4=BF=9D=E6=8C=81=E3=81=97?= =?UTF-8?q?=E3=81=9F=E3=81=BE=E3=81=BE=E5=88=A5=E3=81=AE=E3=83=AA=E3=83=9D?= =?UTF-8?q?=E3=82=B8=E3=83=88=E3=83=AA=E3=81=AB=E7=A7=BB=E5=8B=95=E3=81=95?= =?UTF-8?q?=E3=81=9B=E3=82=8B=E6=96=B9=E6=B3=95=E3=80=8D=E3=82=92=E5=9F=B7?= =?UTF-8?q?=E7=AD=86=E3=83=BB=E5=85=AC=E9=96=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...move-to-another-repository-with-history.md | 252 ++++++++++++++++++ 1 file changed, 252 insertions(+) create mode 100644 articles/git-move-to-another-repository-with-history.md diff --git a/articles/git-move-to-another-repository-with-history.md b/articles/git-move-to-another-repository-with-history.md new file mode 100644 index 0000000..e48ff2d --- /dev/null +++ b/articles/git-move-to-another-repository-with-history.md @@ -0,0 +1,252 @@ +--- +title: "特定のファイル・ディレクトリのコミット履歴を保持したまま別のリポジトリに移動させる方法" +emoji: "🐙" +type: "tech" # tech: 技術記事 / idea: アイデア +topics: ["git"] +published: true +--- + +# 読むのがめんどくさい人向け +```shell: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 で管理しているのにこれだと履歴が途絶えてしまいます。 + +今回はファイルやディレクトリに基づくコミット履歴を保持しつつコピーする方法について紹介します。また、コピー先でパスを改変する方法についても併せて解説します。 + +# 想定ケース +説明を分かりやすくするため、ホームディレクトリを作業スペースとします。 + +```mermaid +flowchart TB + subgraph home[home directory] + subgraph proj_a[proj_a] + subgraph dir_a[dir_a] + file_a[want_to_move.txt] + end + + git_a[.git/] + end + + subgraph proj_b[proj_b] + file_b[want_to_move.txt] + git_b[.git/] + end + + subgraph tmp[tmp] + patch_a[0001-foo.patch] + patch_b[0002-bar.patch] + patch_c[0003-baz.patch] + end + end + + file_a -- history --> patch_a + file_a -- history --> patch_b + file_a -- history --> patch_c + + patch_a -- history --> file_b + patch_b -- history --> file_b + patch_c -- history --> file_b +``` + +* 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:shell +mkdir -p ~/proj_a/dir_a +mkdir ~/proj_b +mkdir ~/tmp +``` + +`~/proj_a`, `~/proj_b` は Git ディレクトリなので Git の準備を行います。 + +```shell:shell +cd ~/proj_a +git init + +cd ~/proj_b +git init +``` + +`~/proj_a/dir_a/want_to_move.txt` を作り、適当にファイルの中身を編集してコミットします。 + +```shell: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:shell +cd ~/proj_a +git log +``` + +``` +commit c1a2f935cdae186c877858e0f618daa864061db2 +Author: user +Date: Sat Jan 27 02:45:00 2024 +0900 + + baz + +commit ea2783d901c4f8539579507d3a0a7010f9abf35e +Author: user +Date: Sun Jan 21 01:20:59 2024 +0900 + + bar + +commit d3367c44334d7a3d39f455bd04b2d363c59ab282 +Author: user +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:shell +cd ~/proj_a +git format-patch --root -o ~/tmp dir_a/want_to_move.txt +``` + +`~/tmp` にパッチファイルが作成されます。`want_to_move.txt` を編集するコミットを 3 つ作ったので 3 つのファイルが生成されているはずです。 + +```shell: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:shell +cd ~/tmp +find . -type f -exec sed -i 's/dir_a\/want_to_move.txt/want_to_move.txt/g' {} + +``` + +同じパスで良い場合は上記のコマンドはスキップしてください。 + +最後に `~/proj_b` でパッチファイルを適用します。 + +```shell:shell +cd ~/proj_b +git am ~/tmp/* +``` + +これで `want_to_move.txt` の履歴を保持したまま `~/proj_b/want_to_move.txt` としてファイルをコピーすることができました。 + +```shell:shell +cd ~/proj_b +git log +``` + +``` +commit c1a2f935cdae186c877858e0f618daa864061db2 +Author: user +Date: Sat Jan 27 02:45:00 2024 +0900 + + baz + +commit ea2783d901c4f8539579507d3a0a7010f9abf35e +Author: user +Date: Sun Jan 21 01:20:59 2024 +0900 + + bar + +commit d3367c44334d7a3d39f455bd04b2d363c59ab282 +Author: user +Date: Wed Jan 17 07:40:42 2024 +0900 + + foo +``` + +# お片付け +作業が完了したら `~/tmp` は削除しても大丈夫です。 + +```shell:shell +rm -rf ~/tmp +``` + +# 一般的な説明 +上記ではファイルをコピーすることを例に挙げましたが、実際にはディレクトリを丸ごとコピーすることもできます。その場合はもちろんそのディレクトリ内にあるファイル・ディレクトリに関わるすべてのコミット履歴を保持するパッチファイルが作成されます。 + +特定のファイル・ディレクトリに関わるすべてのコミット履歴をパッチファイルとして特定のディレクトリ内に出力するには以下のコマンドを実行します。 + +```shell:shell +cd <コピー元リポジトリ> +git format-patch --root -o <パッチファイルの出力先> <対象ファイル・ディレクトリ> +``` + +コピー先リポジトリでパスを改変したい場合は以下のコマンドを実行します。 + +```shell:shell +cd <パッチファイルの出力先> +find . -type f -exec sed -i 's/<変換前のパス>/<変換後のパス>/g' {} + +``` + +ディレクトリ構造を表す `/` は `\/` に変換する必要があります。たとえば `dir_a/want_to_move.txt` は `dir_a\/want_to_move.txt` とする必要があります。 + +パッチファイルを適用するには以下のコマンドを実行します。 + +```shell:shell +cd <コピー先リポジトリ> +git am <パッチファイルの出力先>/* +``` + +# 参考 +* [Gitで特定のディレクトリをコミット履歴を保持したまま別のリポジトリに移動させたい](https://zenn.dev/oyasumi731/articles/5c6cf1573def63) +* [Git Patch Path Change](https://chat.openai.com/share/49c78161-2a7c-4903-a5fc-3a1dc02ebfac)