Closed2

Git リポジトリー2つを1つにまとめる

Jumpei OgawaJumpei Ogawa

2つの Git リポジトリーを1つのリポジトリーにまとめるための一連の git コマンドです。
repo1 に対して repo2 の履歴を取り込むことを想定しています。

REPO1_URL=git@github.com:johndoe/repo1.git
REPO2_URL=git@github.com:janedoe/repo2.git
REPO2_REMOTE=repo2-origin
REPO2_BRANCH=repo2-branch
DESTINATION_PATH=./packages/pkg2
DESTINATION_TOPLEVEL=packages

$ cd path/to/repo1

# マージしたいリポジトリーを remote に登録する
$ git remote add $REPO2_REMOTE $REPO2_URL
$ git fetch $REPO2_REMOTE
# repo2 のマージしたいブランチに切り替え
$ git switch --create $REPO2_BRANCH "${REPO2_REMOTE}/main"

# Optional: コミットログ中を含め、ルートディレクトリーにあるファイルを $DESTINATION_PATH の
# ディレクトリー以下に移動させておく
# 例えば .gitignore が repo1 と repo2 の両方にあった場合、どちらかで上書きされることを回避するため
# find コマンドの方の $DESTINATION_PATH の末尾にはスラッシュが付くことに注意
# (☓ ./packages/pkg2 ○ ./packages/pkg2/)
$ git rebase --root --exec="mkdir --parents ${DESTINATION_PATH} && find -maxdepth 1 -mindepth 1 -not -name $DESTINATION_TOPLEVEL -not -name .git -exec git mv {} ${DESTINATION_PATH}/ \; && git commit --amend --no-edit"

# rebase すると全てのコミットに自分がコミッターとして追加されてしまうので、自分を除去する
$ git filter-branch --commit-filter 'export GIT_COMMITTER_NAME="$GIT_AUTHOR_NAME"; export GIT_COMMITTER_EMAIL="$GIT_AUTHOR_EMAIL"; export GIT_COMMITTER_DATE="$GIT_AUTHOR_DATE"; git commit-tree "$@"'


# repo1 の main ブランチに切り替え、repo2 のブランチをマージする。
# 全く別のリポジトリーをマージするには、--allow-unrelated-histories というオプションが必要
$ git switch main
$ git merge $REPO2_BRANCH --allow-unrelated-histories

# git filter-repo を使うと origin が remote から削除されるので復旧
$ git remote add origin $REPO1_URL
Jumpei OgawaJumpei Ogawa

上記手順の途中で、git filter-repo を用いて、repo2 の .gitignore をコミットログ含めて削除したところ、repo1 の元々の .gitignore も消えてしまった。
参考までに git filter-repo を用いてコミットログを弄りたい場合の手順も以下に示す。

REPO1_URL=git@github.com:johndoe/repo1.git
REPO2_URL=git@github.com:janedoe/repo2.git
REPO2_REMOTE=repo2-origin
REPO2_BRANCH=repo2-branch
DESTINATION_PATH=./packages/pkg2
DESTINATION_TOPLEVEL=packages

#
# repo2 側の整理
#

# まずは repo2 を別途 clone する
$ git clone $REPO2_URL
$ cd path/to/repo2/

# git filter-repo で不要なファイルを履歴から削除する
$ git filter-repo --invert-paths --path path/to/unwanted/files.txt
# ...
# ディレクトリー移動
$ git rebase --root --exec="mkdir --parents $DESTINATION_PATH && find -maxdepth 1 -mindepth 1 -not -name $DESTINATION_TOPLEVEL -not -name .git -exec git mv {} $DESTINATION_PATH/ \; && git commit --amend --no-edit"
# 自分をコミッターから外す
$ git filter-branch --commit-filter 'export GIT_COMMITTER_NAME="$GIT_AUTHOR_NAME"; export GIT_COMMITTER_EMAIL="$GIT_AUTHOR_EMAIL"; export GIT_COMMITTER_DATE="$GIT_AUTHOR_DATE"; git commit-tree "$@"'

#
# repo2 の main ブランチを repo1 にブランチとして取り込み
#

# repo2 を bare リポジトリーに変換
$ git clone --mirror path/to/repo2 path/to/repo2-bare
# repo2 を repo1 の remote として登録
$ git remote add $REPO2_REMOTE path/to/repo2-bare
$
# 後は上の方法と同じように、repo2 のマージしたいブランチを取り込み、
# main ブランチからそれをマージすれば OK
$ git fetch $REPO2_BRANCH
$ git switch --create $REPO2_BRANCH "${REPO2_REMOTE}/main"
$ git switch main
$ git merge $REPO2_BRANCH --allow-unrelated-histories
このスクラップは2023/03/07にクローズされました