ファイル履歴を壊さずに2つのGitリポジトリをマージする


226

2つのGitリポジトリをまったく新しい3番目のリポジトリにマージする必要があります。サブツリーのマージを使用してこれを行う方法の多くの説明を見つけました(たとえば、Jakub Nar mergebskiの2つのGitリポジトリどのようにマージしますか?)古いリポジトリからのファイルは、新しい追加ファイルとして記録されます。実行すると、古いリポジトリのコミット履歴を表示できますが、表示すると、そのファイルの1つのコミット(サブツリーマージ)しか表示されgit logませんgit log <file>。上記の回答に対するコメントから判断すると、この問題を目にしたのは私だけではありませんが、その解決策は公開されていません。

リポジトリをマージして個々のファイル履歴をそのままにしておく方法はありますか?


私はGitを使用していませんが、Mercurialでは必要に応じて変換を実行して、マージするリポジトリのファイルパスを修正し、次に1つのリポジトリを強制的にターゲットにプルして変更セットを取得し、次に異なるブランチのマージ。これはテストされて機能します;)多分これはGitの解決策を見つけるのにも役立ちます...サブツリーのマージアプローチと比較して、パスをマッピングするだけでなく、履歴を書き換える変換ステップが異なると思います(理解している場合)正しく)。これにより、ファイルパスを特別に処理することなくスムーズにマージできます。
Lucero、2012年

また、私はこの質問役に立ったstackoverflow.com/questions/1683531/...
nacross

フォローアップの質問を作成しました。面白いかもしれません:2つのGitリポジトリをマージし、マスターの歴史を保つ:stackoverflow.com/questions/42161910/...
ディミトリDewaele

私のために機能した自動化ソリューションは、stackoverflow.com
a / 30781527/239408

回答:


269

単純に2つのリポジトリを接着して、外部依存関係を管理するのではなく、ずっとそうであるように見せようとすると、答えははるかに簡単になります。リモートを古いリポジトリに追加し、それらを新しいマスターにマージし、ファイルとフォルダーをサブディレクトリに移動し、移動をコミットし、すべての追加のリポジトリについて繰り返すだけです。サブモジュール、サブツリーのマージ、およびファンシーなリベースは、少し異なる問題を解決することを目的としており、私がやろうとしていたことには適していません。

以下は、2つのリポジトリを接着するPowershellスクリプトの例です。

# Assume the current directory is where we want the new repository to be created
# Create the new repository
git init

# Before we do a merge, we have to have an initial commit, so we'll make a dummy commit
git commit --allow-empty -m "Initial dummy commit"

# Add a remote for and fetch the old repo
git remote add -f old_a <OldA repo URL>

# Merge the files from old_a/master into new/master
git merge old_a/master --allow-unrelated-histories

# Move the old_a repo files and folders into a subdirectory so they don't collide with the other repo coming later
mkdir old_a
dir -exclude old_a | %{git mv $_.Name old_a}

# Commit the move
git commit -m "Move old_a files into subdir"

# Do the same thing for old_b
git remote add -f old_b <OldB repo URL>
git merge old_b/master --allow-unrelated-histories
mkdir old_b
dir exclude old_a,old_b | %{git mv $_.Name old_b}
git commit -m "Move old_b files into subdir"

明らかにそれを行う場合は、代わりにold_bをold_aにマージして(新しい結合されたリポジトリになります)、スクリプトを適切に変更します。

進行中の機能ブランチも持ち越したい場合は、これを使用します。

# Bring over a feature branch from one of the old repos
git checkout -b feature-in-progress
git merge -s recursive -Xsubtree=old_a old_a/feature-in-progress

これはプロセスの明白でない部分です。これはサブツリーマージではなく、ターゲットの名前を変更したことをGitに通知し、Gitがすべてを正しく整列させるのに役立つ、通常の再帰的マージに対する引数です。

ここでもう少し詳しく説明します


16
このソリューションを使用git mvしてもうまくいきません。後でgit log移動したファイルの1つでa を使用すると、移動からのコミットのみが取得されます。以前の履歴はすべて失われます。これはgit mv本当にですgit rm; git add、1つのステップです。
mholm815 2013

15
これは、Gitの他の移動/名前変更操作と同じです。コマンドラインからを実行すると、すべての履歴を取得できますgit log --follow。または、すべてのGUIツールで自動的に実行されます。サブツリーのマージでは、私の知る限り、個々のファイルの履歴を取得できないため、この方法の方が優れています。
エリックリー

3
@EricLee old_bリポジトリがマージされると、多くのマージ競合が発生します。それは予想されますか?CONFLICT(名前の変更/削除)
2014年

9
「dir -exclude old_a |%{git mv $ _。Name old_a}」を実行しようとすると、sh.exe ":dir:command not found and sh.exe":git:command not foundが表示されます。これは機能します:ls -I old_a | xargs -I '{}' git mv '{}' old_a /
George

5
これは1(ナンバーワン)でlsあり、資本の「目」ですxargs。このヒントをありがとう!
Dominique Vial

149

履歴を書き換えない方法を以下に示します。すべてのコミットIDは有効なままです。最終結果は、2番目のリポジトリのファイルがサブディレクトリに配置されることです。

  1. 2番目のリポジトリをリモートとして追加します。

    cd firstgitrepo/
    git remote add secondrepo username@servername:andsoon
    
  2. secondrepoのコミットをすべてダウンロードしたことを確認します。

    git fetch secondrepo
    
  3. 2番目のリポジトリのブランチからローカルブランチを作成します。

    git branch branchfromsecondrepo secondrepo/master
    
  4. すべてのファイルをサブディレクトリに移動します。

    git checkout branchfromsecondrepo
    mkdir subdir/
    git ls-tree -z --name-only HEAD | xargs -0 -I {} git mv {} subdir/
    git commit -m "Moved files to subdir/"
    
  5. 2番目のブランチを最初のリポジトリのマスターブランチにマージします。

    git checkout master
    git merge --allow-unrelated-histories branchfromsecondrepo
    

リポジトリには複数のルートコミットがありますが、問題はありません。


1
ステップ2は機能しません:致命的:有効なオブジェクト名ではありません: 'secondrepo / master'。
キース

@キース:2番目のリポジトリを「secondrepo」という名前のリモートとして追加し、そのリポジトリに「マスター」という名前のブランチがあることを確認します(リモートリポジトリのブランチはコマンドで表示できますgit remote show secondrepo
Flimm

私もそれをダウンさせるためにフェッチをしなければなりませんでした。1と2の間でgit fetch
secondrepoを実行しました

@monkjack:私はgit fetchステップを含むように私の回答を編集しました。今後は自由に回答を編集してください。
Flimm 2014年

4
@MartijnHeemels古いバージョンのGitの場合は、省略し--allow-unrelated-historiesます。この回答投稿の履歴をご覧ください。
Flimm 2017

8

数年が経過し、十分に根拠のある投票されたソリューションがありますが、以前のリポジトリから履歴を削除せずに2つのリモートリポジトリを新しいリポジトリにマージしたかったので少し違っていたので、は共有したいと思います。

  1. Githubに新しいリポジトリを作成します。

    ここに画像の説明を入力してください

  2. 新しく作成したリポジトリをダウンロードして、古いリモートリポジトリを追加します。

    git clone https://github.com/alexbr9007/Test.git
    cd Test
    git remote add OldRepo https://github.com/alexbr9007/Django-React.git
    git remote -v
    
  3. 古いリポジトリからすべてのファイルをフェッチして、新しいブランチを作成します。

    git fetch OldRepo
    git branch -a
    

    ここに画像の説明を入力してください

  4. masterブランチで、マージを実行して、古いリポジトリと新しく作成したリポジトリを結合します。

    git merge remotes/OldRepo/master --allow-unrelated-histories
    

    ここに画像の説明を入力してください

  5. OldRepoから追加されたすべての新しく作成されたコンテンツを格納する新しいフォルダーを作成し、そのファイルをこの新しいフォルダーに移動します。

  6. 最後に、結合されたリポジトリからファイルをアップロードし、GitHubからOldRepoを安全に削除できます。

これがリモートリポジトリのマージを扱う誰にとっても役立つことを願っています。


1
これは私がgitの履歴を保存するために機能した唯一のソリューションです。で古いリポジトリへのリモートリンクを削除することを忘れないでくださいgit remote rm OldRepo
Harubiyori

7

使ってみてください

git rebase --root --preserve-merges --onto

人生の早い段階で2つの履歴をリンクする。

パスが重なっている場合は、次の方法で修正します

git filter-branch --index-filter

ログを使用するときは、「コピーをよりハードに見つける」ようにしてください。

git log -CC

そうすれば、パス内のファイルの動きを見つけることができます。



7

私はこれを@Flimm のソリューションから次のgit aliasようなものに変更しました(私のに追加されました~/.gitconfig):

[alias]
 mergeRepo = "!mergeRepo() { \
  [ $# -ne 3 ] && echo \"Three parameters required, <remote URI> <new branch> <new dir>\" && exit 1; \
  git remote add newRepo $1; \
  git fetch newRepo; \
  git branch \"$2\" newRepo/master; \
  git checkout \"$2\"; \
  mkdir -vp \"${GIT_PREFIX}$3\"; \
  git ls-tree -z --name-only HEAD | xargs -0 -I {} git mv {} \"${GIT_PREFIX}$3\"/; \
  git commit -m \"Moved files to '${GIT_PREFIX}$3'\"; \
  git checkout master; git merge --allow-unrelated-histories --no-edit -s recursive -X no-renames \"$2\"; \
  git branch -D \"$2\"; git remote remove newRepo; \
}; \
mergeRepo"

12
好奇心旺盛ですが、本当にエイリアスを必要とするほど頻繁にこれを行っていますか?
パーカーコーツ

1
いいえ、その方法は覚えていませんが、覚えておく必要はないので、エイリアスは覚える方法の1つにすぎません。
Fredrik Erlandsson 2016

1
ええ..しかし、コンピュータを変更してエイリアスを移動するのを忘れてみてください;)
quetzalcoatl

1
の価値は$GIT_PREFIX何ですか?
neowulf33

github.com/git/git/blob/… 元の現在のディレクトリから「git rev-parse --show-prefix」を実行すると、「GIT_PREFIX」が返されます。linkgit:git-rev-parse [1]を参照してください。
Fredrik Erlandsson

3

この関数は、リモートリポジトリをローカルリポジトリディレクトリに複製します。

function git-add-repo
{
    repo="$1"
    dir="$(echo "$2" | sed 's/\/$//')"
    path="$(pwd)"

    tmp="$(mktemp -d)"
    remote="$(echo "$tmp" | sed 's/\///g'| sed 's/\./_/g')"

    git clone "$repo" "$tmp"
    cd "$tmp"

    git filter-branch --index-filter '
        git ls-files -s |
        sed "s,\t,&'"$dir"'/," |
        GIT_INDEX_FILE="$GIT_INDEX_FILE.new" git update-index --index-info &&
        mv "$GIT_INDEX_FILE.new" "$GIT_INDEX_FILE"
    ' HEAD

    cd "$path"
    git remote add -f "$remote" "file://$tmp/.git"
    git pull "$remote/master"
    git merge --allow-unrelated-histories -m "Merge repo $repo into master" --edit "$remote/master"
    git remote remove "$remote"
    rm -rf "$tmp"
}

使い方:

cd current/package
git-add-repo https://github.com/example/example dir/to/save

通知。このスクリプトはコミットを書き換えることができますが、すべての作成者と日付を保存します。つまり、新しいコミットには別のハッシュが含まれます。変更をリモートサーバーにプッシュしようとすると、強制キーでのみ可能であり、サーバー上のコミットも書き換えます。起動する前にバックアップを作成してください。

利益!


私はbashではなくzshを使用しており、gitのv2.13.0を使用しています。何を試しても、git filter-branch --index-filter仕事に就けませんでした。通常、.newインデックスファイルが存在しないというエラーメッセージが表示されます。ベルが鳴りますか?
Patrick Beard

@PatrickBeard zshはわかりません。git-add-repo.sh上記の関数を使用して別のファイルを作成できますgit-add-repo "$@"。ファイルの最後に次の行を挿入します。あなたはzshのようにそれを使用することができた後cd current/git/packagebash path/to/git-add-repo.sh https://github.com/example/example dir/to/save
アンドレイIzman

問題はここで議論されました: stackoverflow.com/questions/7798142/… mv "$GIT_INDEX_FILE.new" "$GIT_INDEX_FILE"時々失敗するので、を追加する必要がありif testます。
Patrick Beard

1
この方法は使用しません。私はスクリプトを素朴かつ逐語的に試しました(その部分については自分のせいにすることができるだけです)、それは私のローカルgitリポジトリを壊しました。履歴はほぼ正しいように見えましたが、Githubへのgitプッシュバックを実行すると、恐ろしい「RPCが失敗しました。curl55 SSL_write()がSYSCALL、errno = 32を返しました」というエラーが発生しました。修理しようとしたのですが、どうしても壊れてしまいました。結局、新しいローカルリポジトリで物事を再構築する必要がありました。
メイソンフリード

@MasonFreedこのスクリプトは、両方のリポジトリを組み合わせて新しいgit履歴を作成するため、古いリポジトリにプッシュすることはできません。新しいリポジトリを作成するか、強制キーでプッシュする必要があります。つまり、サーバーでリポジトリを書き換えます
Andrey Izman

2

手順に従って、1つのリポジトリを別のリポジトリに埋め込み、両方のgit履歴をマージして1つのgit履歴を作成します。

  1. マージする両方のリポジトリのクローンを作成します。

git clone git@github.com:user / parent-repo.git

git clone git@github.com:user / child-repo.git

  1. 子リポジトリに移動

cd child-repo /

  1. 以下のコマンドを実行し、パスmy/new/subdir(3つのオカレンス)を、子リポジトリを置きたいディレクトリ構造に置き換えます。

git filter-branch --prune-empty --tree-filter 'if [!-e my / new / subdir]; 次にmkdir -p my / new / subdir git ls-tree --name-only $ GIT_COMMIT | xargs -Iファイルmvファイルmy / new / subdir fi '

  1. 親リポジトリに移動

cd ../parent-repo/

  1. 親リポジトリにリモートを追加し、子リポジトリへのパスを指す

git remote add child-remote ../child-repo/

  1. 子リポジトリを取得する

git fetch child-remote

  1. 履歴をマージする

git merge --allow-unrelated-histories子リモート/マスター

ここで親リポジトリのgitログを確認すると、子リポジトリのコミットがマージされているはずです。コミットソースから示すタグも確認できます。

以下の記事は、1つのリポジトリを別のリポジトリに埋め込むときに、両方のgit履歴をマージすることで1つのgit履歴を保持するのに役立ちました。

http://ericlathrop.com/2014/01/combining-git-repositories/

お役に立てれば。ハッピーコーディング!


ステップ3は構文エラーで失敗しました。セミコロンがありません。修正git filter-branch --prune-empty --tree-filter ' if [ ! -e my/new/subdir ]; then mkdir -p my/new/subdir; git ls-tree --name-only $GIT_COMMIT | xargs -I files mv files my/new/subdir; fi'
Yuri L

1

リポジトリをマージしたいと言うab(私は彼らが互いに並んで位置していると仮定しています):

cd b
git remote add a ../a
git fetch a
git merge --allow-unrelated-histories a/master
git remote remove a

aサブディレクトリに配置する場合は、上記のコマンドの前に次の手順を実行します。

cd a
git filter-repo --to-subdirectory-filter a
cd ..

このためには必要git-filter-repo(インストールfilter-branchされてがっかり)。

2つの大きなリポジトリをマージし、そのうちの1つをサブディレクトリに配置する例:https : //gist.github.com/x-yuri/9890ab1079cf4357d6f269d073fd9731

詳細はこちら

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.