2つのGitリポジトリをどのようにマージしますか?


1622

次のシナリオを検討してください。

独自のGitリポジトリで小規模な実験プロジェクトAを開発しました。現在は成熟しており、Aは、独自の大きなリポジトリを持つ大規模なプロジェクトBの一部になりたいと考えています。次に、AをBのサブディレクトリとして追加します。

どのようにしても履歴を失うことなく、AをBにマージするにはどうすればよいですか?


8
:あなたはちょうどこの質問を見て持って、両方のリポジトリを維持するために必要とせずに、一つに2つのリポジトリを統合しようとしている場合はstackoverflow.com/questions/13040958/...を
Flimm

カスタムディレクトリでgitリポジトリをマージしてすべてのコミットを保存するには、stackoverflow.com
Andrey Izman

回答:


437

別のリポジトリの単一のブランチは、その履歴を保持するサブディレクトリの下に簡単に配置できます。例えば:

git subtree add --prefix=rails git://github.com/rails/rails.git master

これは、Railsマスターブランチのすべてのファイルが「rails」ディレクトリに追加される単一のコミットとして表示されます。ただし、コミットのタイトルには古い履歴ツリーへの参照が含まれています。

コミットから「rails /」を追加する <rev>

<rev>SHA-1コミットハッシュはどこにありますか。あなたはまだ歴史を見ることができます、いくつかの変更のせいです。

git log <rev>
git blame <rev> -- README.md

これはディレクトリのプレフィックスが表示されないことに注意してください。これは、実際の古いブランチがそのまま残っているためです。これは通常のファイル移動コミットのように扱う必要があります。到達すると、追加のジャンプが必要になります。

# finishes with all files added at once commit
git log rails/README.md

# then continue from original tree
git log <rev> -- README.md

これを手動で行う、または他の回答で説明されているように履歴を書き換えるなど、より複雑な解決策があります。

git-subtreeコマンドは公式のgit-contribの一部であり、一部のパケットマネージャーはデフォルトでインストールします(OS X Homebrew)。ただし、gitに加えて、自分でインストールする必要がある場合があります。


2
ここで(2013年6月現在)Gitのサブツリーをインストールする方法については、次のとおりです。stackoverflow.com/a/11613541/694469は (と私は交換しgit co v1.7.11.3 ... v1.8.3)。
KajMagnus 2013年

1
以下の回答についてヘッドアップしていただきありがとうございます。git 1.8.4以降、「サブツリー」はまだ含まれていません(少なくともUbuntu 12.04 git ppa(ppa:git-core / ppa)には含まれていません)
Matt Klein

1
この後git log rails/somefile、マージコミット以外のそのファイルのコミット履歴が表示されないことを確認できます。@artfulrobotが示唆したように、Greg Hewgillの回答を確認してください。そして、あなたがgit filter-branch含めたいレポで使用する必要があるかもしれません。
Jifeng Zhang 2013

6
それともエリック・リーさんは、「ファイルの履歴失うことなく、二つのGitリポジトリの中へ1つのリポジトリをマージする」をお読みsaintgimp.org/2013/01/22/...
Jifeng張

4
他の人が言ったように、git subtreeあなたが思うことをしないかもしれません!より完全なソリューションについては、こちらをご覧ください。
Paul Draper 14

1908

にマージproject-aしたい場合project-b

cd path/to/project-b
git remote add project-a path/to/project-a
git fetch project-a --tags
git merge --allow-unrelated-histories project-a/master # or whichever branch you want to merge
git remote remove project-a

出典:git merge different repositorys?

この方法は私にはかなりうまくいきました、それはより短くそして私の意見ではかなりきれいです。

project-aサブディレクトリに置きたい場合は使用できますgit-filter-repoお勧めしません)。上記のコマンドの前に次のコマンドを実行します。filter-branch

cd path/to/project-a
git filter-repo --to-subdirectory-filter project-a

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

注:この--allow-unrelated-historiesパラメーターはgit> = 2.9以降でのみ存在します。Git-git merge Documentation / --allow-unrelated-historiesを参照してください

更新--tagsタグを保持するために、@ jstadlerによって提案されたように追加されました。


8
これは私のために仕事をしました。初めて.gitignoreファイルで競合が1つだけ発生したチャームのように動作しました!コミット履歴を完全に保存しました。単純化に加えて、他のアプローチに対する大きなプラスは、これにより、マージされたリポジトリへの継続的な参照が不要になることです。ただし、気をつけなければならないことの1つは、私のようなiOS開発者である場合は、ターゲットリポジトリのプロジェクトファイルをワークスペースに慎重にドロップすることです。
Max MacLeod

30
ありがとう。私のために働いた。マージしたディレクトリをサブフォルダーに移動する必要があったので、上記の手順を実行した後、私は単純に使用しましたgit mv source-dir/ dest/new-source-dir
Sid

13
このgit mergeステップはここで失敗しfatal: refusing to merge unrelated historiesます。docsで--allow-unrelated-histories説明されているように修正します。
ssc

19
--allow-unrelated-historiesgit 2.9で導入されました。以前のバージョンでは、デフォルトの動作でした。
Douglas Royds

11
より短い:git fetch /path/to/project-a master; git merge --allow-unrelated-histories FETCH_HEAD
jthill 2017年

614

2つの可能な解決策は次のとおりです。

サブモジュール

リポジトリAを大きなプロジェクトBの別のディレクトリにコピーするか、(おそらくより良い)リポジトリAをプロジェクトBのサブディレクトリに複製します。次に、gitサブモジュールを使用して、このリポジトリをリポジトリBのサブモジュールにします。

これは、リポジトリAで開発が続け疎結合リポジトリに適したソリューションで、開発の大部分は、Aも参照してください内の別のスタンドアロン開発でSubmoduleSupportGitSubmoduleTutorialのGitのWikiのページ。

サブツリーのマージ

サブツリーマージ戦略を使用して、リポジトリAをプロジェクトBのサブディレクトリにマージできます。これについては、サブツリーのマージと Markus Prinzによる解説で説明されています

git remote add -f Bproject /path/to/B
git merge -s ours --allow-unrelated-histories --no-commit Bproject/master
git read-tree --prefix=dir-B/ -u Bproject/master
git commit -m "Merge B project as our subdirectory"
git pull -s subtree Bproject master

--allow-unrelated-historiesGit> = 2.9.0ではオプションが必要です。)

または、たとえば彼のブログ投稿「Gitサブモジュールの新しい代替:git subtree」で発表されたapenwarr(Avery Pennarun)によるgitサブツリーツール(GitHubのリポジトリ)を使用できます。


私はあなたの場合(Aはより大きなプロジェクトBの一部であると思います)、正しい解決策はsubtree mergeを使用することだと思います


1
これは機能し、履歴を保持しているように見えますが、ファイルを比較したり、マージを二分するために使用することはできません。足りない?
jettero

56
これは不完全です。はい、たくさんのコミットを取得しますが、それらは正しいパスを参照しなくなります。git log dir-B/somefile1つのマージ以外は何も表示されません。グレッグ・ヒューギルの回答がこの重要な問題を参照しているのを見てください。
artfulrobot

2
重要:git pull --no-rebase -sサブツリーBprojectマスターこれを行わない場合、プルが自動的にリベースされるように設定すると、「オブジェクトを解析できませんでした」という結果になります。osdir.com/ml/git/2009-07/msg01576.html
Eric Bowman-abstracto-

4
質問ではAだったときに、マージされたサブツリーとしてBが含まれているため、この回答は混乱する可能性があります。コピーと貼り付けの結果はどうですか。
vfclists 2012

11
2つのリポジトリを単純に接着しようとする場合、サブモジュールとサブツリーのマージは、他のコメンターが指摘しているように、すべてのファイル履歴を保持しないため、使用するのに不適切なツールです。stackoverflow.com/questions/13040958/…を参照してください。
エリックリー

194

プロジェクトを個別に維持したい場合は、サブモジュールアプローチが適しています。ただし、両方のプロジェクトを同じリポジトリにマージしたい場合は、もう少し作業が必要です。

まず最初git filter-branchに、2番目のリポジトリのすべての名前を、最終的に必要なサブディレクトリにあるように書き換えます。したがって、の代わりにfoo.cbar.htmlとが必要にprojb/foo.cなりprojb/bar.htmlます。

その後、次のようなことができるはずです。

git remote add projb [wherever]
git pull projb

git pull行いますgit fetchが続きをgit merge。プルしようとしているリポジトリにまだprojb/ディレクトリがない場合、競合は発生しません。

さらに検索すると、にマージgitkするために同様のことが行われたことが示されgitます。Junio C Hamanoがそれについてここに書いています:http : //www.mail-archive.com/git@vger.kernel.org/msg03395.html


4
サブツリーのマージはより良い解決策であり、インクルードされたプロジェクトの履歴を書き換える必要はありません
JakubNarębski'09 / 09/15

8
git filter-branchこれを実現するための使い方を知りたいのですが。manページでは、逆の方法について述べています:subdir /をルートにするが、その逆はしません。
artfulrobot

31
フィルター分岐を使用して目的の結果を達成する方法を説明している場合、この答えは素晴らしいでしょう
Anentropic

14
私はここで、フィルタ分岐を使用する方法が見つかりました:stackoverflow.com/questions/4042816/...を
デヴィッド・マイナー

3
Gregの概要の実装については、この回答を参照してください。
Paul Draper 14

75

git-subtree いいですが、おそらくあなたが望むものではありません。

たとえば、projectAがBで作成されたディレクトリである場合git subtree

git log projectA

、1つのコミット、つまりマージのみをリストしています。マージされたプロジェクトからのコミットは異なるパス用であるため、表示されません。

グレッグ・ヒューギルの答えが最も近くなりますが、実際にはパスを書き換える方法は述べられていません。


ソリューションは驚くほど簡単です。

(1)Aでは、

PREFIX=projectA #adjust this

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

注:これにより履歴が書き換えられるため、このリポジトリAを引き続き使用する場合は、まずその使い捨てコピーを複製(コピー)することをお勧めします。

注Bene:ファイル名またはパスに非ASCII文字(または白い文字)を使用する場合は、sedコマンド内の代替スクリプトを変更する必要があります。その場合、「ls-files -s」によって作成されたレコード内のファイルの場所は引用符で始まります。

(2)次にBで実行

git pull path/to/A

出来上がり!あなたは持っているprojectAあなたが実行している場合はBのディレクトリをgit log projectA使用すると、Aからすべてのコミットが表示されます、


私の場合、2つのサブディレクトリprojectAとが必要projectBでした。その場合は、Bに対しても(1)を行いました。


1
stackoverflow.com/a/618113/586086から回答をコピーしたようです。
Andrew Mao 14

1
@AndrewMao、私はそう思います...私は実際には思い出せません。私はこのスクリプトをかなり使用しました。
Paul Draper 14

6
OS Xでは\ tが機能せず、<tab>を入力する必要があることを追加します
Muneeb Ali

2
"$GIT_INDEX_FILE"引用符で囲む必要があります(2回)。そうでない場合、たとえばパスにスペースが含まれている場合、メソッドは失敗します。
Rob W

4
疑問に思っている場合、osxに<tab>を挿入するには、次の操作が必要Ctrl-V <tab>
です

48

両方のリポジトリに同じ種類のファイルがある場合(異なるプロジェクトの2つのRailsリポジトリなど)、セカンダリリポジトリのデータを現在のリポジトリにフェッチできます。

git fetch git://repository.url/repo.git master:branch_name

そしてそれを現在のリポジトリにマージします:

git merge --allow-unrelated-histories branch_name

Gitのバージョンが2.9よりも小さい場合は、を削除し--allow-unrelated-historiesます。

この後、競合が発生する可能性があります。たとえばで解決できますgit mergetoolkdiff3キーボードだけで使用できるため、コードを数分読み取るだけで5つの競合ファイルが発生します。

マージを完了することを忘れないでください:

git commit

25

マージを使用すると履歴が失われ続けたので、2つのリポジトリはコミットごとにマージしないように十分に異なるので、結局リベースを使用しました。

git clone git@gitorious/projA.git projA
git clone git@gitorious/projB.git projB

cd projB
git remote add projA ../projA/
git fetch projA 
git rebase projA/master HEAD

=>競合を解決してから、必要な回数だけ続行します...

git rebase --continue

これを行うと、1つのプロジェクトがprojAからのすべてのコミットに続いてprojBからのコミットを持つ


25

私の場合は、my-pluginリポジトリとリポジトリがあり、常にのサブディレクトリで開発されているmain-projectふりをしたかったのです。my-pluginpluginsmain-project

基本的に、my-pluginリポジトリの履歴を書き直して、すべての開発がplugins/my-pluginサブディレクトリで行われたように見せました。その後、私はの開発の歴史を追加my-pluginmain-project歴史、そして一緒に2つのツリーをマージ。plugins/my-plugin既に存在するディレクトリがなかったのでmain-projectリポジトリー、これは単純な競合のないマージでした。結果のリポジトリには、両方の元のプロジェクトのすべての履歴が含まれ、2つのルーツがありました。

TL; DR

$ cp -R my-plugin my-plugin-dirty
$ cd my-plugin-dirty
$ git filter-branch -f --tree-filter "zsh -c 'setopt extended_glob && setopt glob_dots && mkdir -p plugins/my-plugin && (mv ^(.git|plugins) plugins/my-plugin || true)'" -- --all
$ cd ../main-project
$ git checkout master
$ git remote add --fetch my-plugin ../my-plugin-dirty
$ git merge my-plugin/master --allow-unrelated-histories
$ cd ..
$ rm -rf my-plugin-dirty

ロングバージョン

まず、my-pluginこのリポジトリの履歴を書き換えるため、リポジトリのコピーを作成します。

次に、my-pluginリポジトリのルートに移動し、メインブランチ(おそらくmaster)をチェックアウトして、次のコマンドを実行します。もちろん、あなたがのために置き換えなければならないmy-pluginplugins、あなたの実際の名前は何でもあります。

$ git filter-branch -f --tree-filter "zsh -c 'setopt extended_glob && setopt glob_dots && mkdir -p plugins/my-plugin && (mv ^(.git|plugins) plugins/my-plugin || true)'" -- --all

説明のために。git filter-branch --tree-filter (...) HEAD実行し(...)、すべてのコマンドがから到達可能であることをコミットHEAD。これは、コミットごとに保存されたデータを直接操作するため、「作業ディレクトリ」、「インデックス」、「ステージング」などの概念について心配する必要がないことに注意してください。

filter-branch失敗したコマンドを実行すると、.gitディレクトリにいくつかのファイルが残り、次に試行filter-branchするときに、に-fオプションを指定しない限り、これについて不満が出ますfilter-branch

実際のコマンドについては、思い通りの操作ができなかったbashため、代わりにコマンドzsh -czsh実行させました。最初に、コマンドextended_glob^(...)構文を有効にするオプションと、グロブ()を使用してドットファイル(など)を選択できるオプションを設定します。mvglob_dots.gitignore^(...)

次に、mkdir -pコマンドを使用して両方pluginsplugins/my-plugin同時に作成します。

最後に、zsh「negative glob」機能^(.git|plugins)を使用して.git、新しく作成されたmy-pluginフォルダーを除いて、リポジトリーのルート・ディレクトリー内のすべてのファイルを突き合わせます。(.gitここでは除外する必要はないかもしれませんが、ディレクトリをそれ自体に移動しようとするとエラーになります。)

私のリポジトリでは、最初のコミットにファイルが含まれていなかったためmv、最初のコミットでコマンドがエラーを返しました(移動できるものが何もないため)。そのため、中止しない|| trueようにを追加しましたgit filter-branch

この--allオプションは、リポジトリ内のすべてのブランチfilter-branchの履歴を書き換えるように指示します。また、ブランチをそれ自体のオプションとしてではなく、書き換えるためのオプションリストの一部として解釈するために追加が必要です。--gitfilter-branch

次に、main-projectリポジトリに移動して、マージするブランチをチェックアウトします。my-pluginリポジトリのローカルコピー(履歴が変更されたもの)を以下のリモートとして追加しますmain-project

$ git remote add --fetch my-plugin $PATH_TO_MY_PLUGIN_REPOSITORY

これで、コミット履歴に2つの無関係なツリーが作成されます。これを使用してうまく視覚化できます。

$ git log --color --graph --decorate --all

それらをマージするには、以下を使用します。

$ git merge my-plugin/master --allow-unrelated-histories

2.9.0より前のGitでは、この--allow-unrelated-historiesオプションは存在しないことに注意してください。これらのバージョンのいずれかを使用している場合は、オプションを省略してください。これを--allow-unrelated-histories防ぐエラーメッセージ2.9.0で追加されました。

マージの競合があってはなりません。その場合は、filter-branchコマンドが正しく機能しなかったか、すでににplugins/my-pluginディレクトリが存在していた可能性がありmain-projectます。

2つのルートを持つリポジトリを作成するためにどのようなハッカーが起こっていたのか疑問に思っている今後の寄稿者には、必ず説明的なコミットメッセージを入力してください。

上記のgit logコマンドを使用して、2つのルートコミットがあるはずの新しいコミットグラフを視覚化できます。ブランチのみmasterがマージされることに注意してください。つまりmy-pluginmain-projectツリーにマージしたい他のブランチで重要な作業がある場合は、my-pluginこれらのマージが完了するまで、リモートを削除しないでください。そうしないと、それらのブランチからのコミットはmain-projectリポジトリに残りますが、一部は到達できず、最終的なガベージコレクションの影響を受けやすくなります。(また、リモートを削除するとそのリモート追跡ブランチが削除されるため、SHAでそれらを参照する必要があります。)

オプションで、保持したいものすべてをマージした後my-plugin、次my-pluginを使用してリモートを削除できます。

$ git remote remove my-plugin

これでmy-plugin、履歴を変更したリポジトリのコピーを安全に削除できます。私の場合、my-pluginマージが完了してプッシュされた後に、実際のリポジトリに非推奨通知も追加しました。


搭載したMac OS Xエルキャピタンでテストgit --version 2.9.0してzsh --version 5.2。あなたのマイレージは異なる場合があります。

参照:


1
どこ--allow-unrelated-historiesから来たの?
xpto 2016

3
@MarceloFilhoチェックman git-mergeデフォルトでは、git mergeコマンドは、共通の祖先を共有しない履歴のマージを拒否します。このオプションは、2つのプロジェクトの履歴を個別に開始したときにマージするときに、この安全性をオーバーライドするために使用できます。これは非常にまれなケースであるため、デフォルトでこれを有効にする構成変数が存在せず、追加されません。
Radon Rosborough、2016

で利用できるはずgit version 2.7.2.windows.1ですか?
xpto

2
@MarceloFilhoこれは2.9.0で追加されましたが、古いバージョンではオプションを渡す必要はありません(機能するだけです)。github.com/git/git/blob/...
ラドンRosborough

これはうまくいきました。また、フィルターブランチを使用して、マージ前にツリー内の目的の場所にファイル名を書き換えることができました。masterブランチ以外に履歴を移動する必要がある場合は、さらに多くの作業が必要になると思います。
codeDr

9

私は何日も同じことをしようとしてきました、私はgit 2.7.2を使用しています。サブツリーは履歴を保存しません。

古いプロジェクトを今後使用しない場合は、この方法を使用できます。

最初にBブランチを作成し、ブランチで作業することをお勧めします。

分岐のない手順は次のとおりです。

cd B

# You are going to merge A into B, so first move all of B's files into a sub dir
mkdir B

# Move all files to B, till there is nothing in the dir but .git and B
git mv <files> B

git add .

git commit -m "Moving content of project B in preparation for merge from A"


# Now merge A into B
git remote add -f A <A repo url>

git merge A/<branch>

mkdir A

# move all the files into subdir A, excluding .git
git mv <files> A

git commit -m "Moved A into subdir"


# Move B's files back to root    
git mv B/* ./

rm -rf B

git commit -m "Reset B to original state"

git push

サブディレクトリAにファイルのログを記録すると、完全な履歴が得られます

git log --follow A/<file>

これは私がこれを行うのに役立つ投稿でした:

http://saintgimp.org/2013/01/22/merging-two-git-repositories-into-one-repository-without-losing-file-history/


8

あなたがレポBに枝からファイルを入れたい場合はサブツリーレポAのも歴史を保存、読み続けます。(以下の例では、レポBのマスターブランチをレポAのマスターブランチにマージすることを想定しています。)

リポジトリAで、まず次の手順を実行して、リポジトリBを使用可能にします。

git remote add B ../B # Add repo B as a new remote.
git fetch B

次に、repo Aに新しいブランチ(1つのコミットのみ)を作成しますnew_b_root。結果のコミットには、リポジトリBのマスターブランチの最初のコミットでコミットされたが、というサブディレクトリに置かれたファイルが含まれpath/to/b-files/ます。

git checkout --orphan new_b_root master
git rm -rf . # Remove all files.
git cherry-pick -n `git rev-list --max-parents=0 B/master`
mkdir -p path/to/b-files
git mv README path/to/b-files/
git commit --date="$(git log --format='%ai' $(git rev-list --max-parents=0 B/master))"

説明:--orphancheckoutコマンドのオプションは、Aのマスターブランチからファイルをチェックアウトしますが、コミットは作成しません。次はとにかくすべてのファイルを消去するので、コミットを選択することができます。次に、まだコミットせずに(-n)、Bのマスターブランチから最初のコミットを選択します。(チェリーピックは、ストレートチェックアウトでは行われないように見える元のコミットメッセージを保持します。)次に、リポジトリBからすべてのファイルを配置するサブツリーを作成します。次に、サブツリーにチェリーピックします。上記の例では、README移動するファイルのみがあります。次に、Bレポルートコミットをコミットし、同時に元のコミットのタイムスタンプも保持します。

次に、B/master新しく作成したの上に新しいブランチを作成しnew_b_rootます。新しいブランチを呼び出すb

git checkout -b b B/master
git rebase -s recursive -Xsubtree=path/to/b-files/ new_b_root

次に、bブランチをにマージしますA/master

git checkout master
git merge --allow-unrelated-histories --no-commit b
git commit -m 'Merge repo B into repo A.'

最後に、Bリモートブランチと一時ブランチを削除できます。

git remote remove B
git branch -D new_b_root b

最終的なグラフは、次のような構造になります。

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


ありがとうございます。Andresch Serjからの「git subtree」または「merge --allow-unrelated-histories」を使用して、サブディレクトリにログがないという他の回答を本当に見逃しました。
Ilendir 2018年

8

ここでStack OverFlowなどに関する多くの情報を収集し、問題を解決するスクリプトをまとめることができました。

注意点は、各リポジトリの「開発」ブランチのみを考慮し、それを完全に新しいリポジトリの別のディレクトリにマージすることです。

タグと他のブランチは無視されます-これはあなたが望むものではないかもしれません。

スクリプトは機能の分岐とタグも処理します-新しいプロジェクトでそれらの名前を変更して、それらがどこから来たかを確認します。

#!/bin/bash
#
################################################################################
## Script to merge multiple git repositories into a new repository
## - The new repository will contain a folder for every merged repository
## - The script adds remotes for every project and then merges in every branch
##   and tag. These are renamed to have the origin project name as a prefix
##
## Usage: mergeGitRepositories.sh <new_project> <my_repo_urls.lst>
## - where <new_project> is the name of the new project to create
## - and <my_repo_urls.lst> is a file contaning the URLs to the respositories
##   which are to be merged on separate lines.
##
## Author: Robert von Burg
##            eitch@eitchnet.ch
##
## Version: 0.3.2
## Created: 2018-02-05
##
################################################################################
#

# disallow using undefined variables
shopt -s -o nounset

# Script variables
declare SCRIPT_NAME="${0##*/}"
declare SCRIPT_DIR="$(cd ${0%/*} ; pwd)"
declare ROOT_DIR="$PWD"
IFS=$'\n'

# Detect proper usage
if [ "$#" -ne "2" ] ; then
  echo -e "ERROR: Usage: $0 <new_project> <my_repo_urls.lst>"
  exit 1
fi


## Script variables
PROJECT_NAME="${1}"
PROJECT_PATH="${ROOT_DIR}/${PROJECT_NAME}"
TIMESTAMP="$(date +%s)"
LOG_FILE="${ROOT_DIR}/${PROJECT_NAME}_merge.${TIMESTAMP}.log"
REPO_FILE="${2}"
REPO_URL_FILE="${ROOT_DIR}/${REPO_FILE}"


# Script functions
function failed() {
  echo -e "ERROR: Merging of projects failed:"
  echo -e "ERROR: Merging of projects failed:" >>${LOG_FILE} 2>&1
  echo -e "$1"
  exit 1
}

function commit_merge() {
  current_branch="$(git symbolic-ref HEAD 2>/dev/null)"
  if [[ ! -f ".git/MERGE_HEAD" ]] ; then
    echo -e "INFO:   No commit required."
    echo -e "INFO:   No commit required." >>${LOG_FILE} 2>&1
  else
    echo -e "INFO:   Committing ${sub_project}..."
    echo -e "INFO:   Committing ${sub_project}..." >>${LOG_FILE} 2>&1
    if ! git commit -m "[Project] Merged branch '$1' of ${sub_project}" >>${LOG_FILE} 2>&1 ; then
      failed "Failed to commit merge of branch '$1' of ${sub_project} into ${current_branch}"
    fi
  fi
}


# Make sure the REPO_URL_FILE exists
if [ ! -e "${REPO_URL_FILE}" ] ; then
  echo -e "ERROR: Repo file ${REPO_URL_FILE} does not exist!"
  exit 1
fi


# Make sure the required directories don't exist
if [ -e "${PROJECT_PATH}" ] ; then
  echo -e "ERROR: Project ${PROJECT_NAME} already exists!"
  exit 1
fi


# create the new project
echo -e "INFO: Logging to ${LOG_FILE}"
echo -e "INFO: Creating new git repository ${PROJECT_NAME}..."
echo -e "INFO: Creating new git repository ${PROJECT_NAME}..." >>${LOG_FILE} 2>&1
echo -e "===================================================="
echo -e "====================================================" >>${LOG_FILE} 2>&1
cd ${ROOT_DIR}
mkdir ${PROJECT_NAME}
cd ${PROJECT_NAME}
git init
echo "Initial Commit" > initial_commit
# Since this is a new repository we need to have at least one commit
# thus were we create temporary file, but we delete it again.
# Deleting it guarantees we don't have conflicts later when merging
git add initial_commit
git commit --quiet -m "[Project] Initial Master Repo Commit"
git rm --quiet initial_commit
git commit --quiet -m "[Project] Initial Master Repo Commit"
echo


# Merge all projects into the branches of this project
echo -e "INFO: Merging projects into new repository..."
echo -e "INFO: Merging projects into new repository..." >>${LOG_FILE} 2>&1
echo -e "===================================================="
echo -e "====================================================" >>${LOG_FILE} 2>&1
for url in $(cat ${REPO_URL_FILE}) ; do

  if [[ "${url:0:1}" == '#' ]] ; then
    continue
  fi

  # extract the name of this project
  export sub_project=${url##*/}
  sub_project=${sub_project%*.git}

  echo -e "INFO: Project ${sub_project}"
  echo -e "INFO: Project ${sub_project}" >>${LOG_FILE} 2>&1
  echo -e "----------------------------------------------------"
  echo -e "----------------------------------------------------" >>${LOG_FILE} 2>&1

  # Fetch the project
  echo -e "INFO:   Fetching ${sub_project}..."
  echo -e "INFO:   Fetching ${sub_project}..." >>${LOG_FILE} 2>&1
  git remote add "${sub_project}" "${url}"
  if ! git fetch --tags --quiet ${sub_project} >>${LOG_FILE} 2>&1 ; then
    failed "Failed to fetch project ${sub_project}"
  fi

  # add remote branches
  echo -e "INFO:   Creating local branches for ${sub_project}..."
  echo -e "INFO:   Creating local branches for ${sub_project}..." >>${LOG_FILE} 2>&1
  while read branch ; do
    branch_ref=$(echo $branch | tr " " "\t" | cut -f 1)
    branch_name=$(echo $branch | tr " " "\t" | cut -f 2 | cut -d / -f 3-)

    echo -e "INFO:   Creating branch ${branch_name}..."
    echo -e "INFO:   Creating branch ${branch_name}..." >>${LOG_FILE} 2>&1

    # create and checkout new merge branch off of master
    if ! git checkout -b "${sub_project}/${branch_name}" master >>${LOG_FILE} 2>&1 ; then failed "Failed preparing ${branch_name}" ; fi
    if ! git reset --hard ; then failed "Failed preparing ${branch_name}" >>${LOG_FILE} 2>&1 ; fi
    if ! git clean -d --force ; then failed "Failed preparing ${branch_name}" >>${LOG_FILE} 2>&1 ; fi

    # Merge the project
    echo -e "INFO:   Merging ${sub_project}..."
    echo -e "INFO:   Merging ${sub_project}..." >>${LOG_FILE} 2>&1
    if ! git merge --allow-unrelated-histories --no-commit "remotes/${sub_project}/${branch_name}" >>${LOG_FILE} 2>&1 ; then
      failed "Failed to merge branch 'remotes/${sub_project}/${branch_name}' from ${sub_project}"
    fi

    # And now see if we need to commit (maybe there was a merge)
    commit_merge "${sub_project}/${branch_name}"

    # relocate projects files into own directory
    if [ "$(ls)" == "${sub_project}" ] ; then
      echo -e "WARN:   Not moving files in branch ${branch_name} of ${sub_project} as already only one root level."
      echo -e "WARN:   Not moving files in branch ${branch_name} of ${sub_project} as already only one root level." >>${LOG_FILE} 2>&1
    else
      echo -e "INFO:   Moving files in branch ${branch_name} of ${sub_project} so we have a single directory..."
      echo -e "INFO:   Moving files in branch ${branch_name} of ${sub_project} so we have a single directory..." >>${LOG_FILE} 2>&1
      mkdir ${sub_project}
      for f in $(ls -a) ; do
        if  [[ "$f" == "${sub_project}" ]] ||
            [[ "$f" == "." ]] ||
            [[ "$f" == ".." ]] ; then
          continue
        fi
        git mv -k "$f" "${sub_project}/"
      done

      # commit the moving
      if ! git commit --quiet -m  "[Project] Move ${sub_project} files into sub directory" ; then
        failed "Failed to commit moving of ${sub_project} files into sub directory"
      fi
    fi
    echo
  done < <(git ls-remote --heads ${sub_project})


  # checkout master of sub probject
  if ! git checkout "${sub_project}/master" >>${LOG_FILE} 2>&1 ; then
    failed "sub_project ${sub_project} is missing master branch!"
  fi

  # copy remote tags
  echo -e "INFO:   Copying tags for ${sub_project}..."
  echo -e "INFO:   Copying tags for ${sub_project}..." >>${LOG_FILE} 2>&1
  while read tag ; do
    tag_ref=$(echo $tag | tr " " "\t" | cut -f 1)
    tag_name_unfixed=$(echo $tag | tr " " "\t" | cut -f 2 | cut -d / -f 3)

    # hack for broken tag names where they are like 1.2.0^{} instead of just 1.2.0
    tag_name="${tag_name_unfixed%%^*}"

    tag_new_name="${sub_project}/${tag_name}"
    echo -e "INFO:     Copying tag ${tag_name_unfixed} to ${tag_new_name} for ref ${tag_ref}..."
    echo -e "INFO:     Copying tag ${tag_name_unfixed} to ${tag_new_name} for ref ${tag_ref}..." >>${LOG_FILE} 2>&1
    if ! git tag "${tag_new_name}" "${tag_ref}" >>${LOG_FILE} 2>&1 ; then
      echo -e "WARN:     Could not copy tag ${tag_name_unfixed} to ${tag_new_name} for ref ${tag_ref}"
      echo -e "WARN:     Could not copy tag ${tag_name_unfixed} to ${tag_new_name} for ref ${tag_ref}" >>${LOG_FILE} 2>&1
    fi
  done < <(git ls-remote --tags --refs ${sub_project})

  # Remove the remote to the old project
  echo -e "INFO:   Removing remote ${sub_project}..."
  echo -e "INFO:   Removing remote ${sub_project}..." >>${LOG_FILE} 2>&1
  git remote rm ${sub_project}

  echo
done


# Now merge all project master branches into new master
git checkout --quiet master
echo -e "INFO: Merging projects master branches into new repository..."
echo -e "INFO: Merging projects master branches into new repository..." >>${LOG_FILE} 2>&1
echo -e "===================================================="
echo -e "====================================================" >>${LOG_FILE} 2>&1
for url in $(cat ${REPO_URL_FILE}) ; do

  if [[ ${url:0:1} == '#' ]] ; then
    continue
  fi

  # extract the name of this project
  export sub_project=${url##*/}
  sub_project=${sub_project%*.git}

  echo -e "INFO:   Merging ${sub_project}..."
  echo -e "INFO:   Merging ${sub_project}..." >>${LOG_FILE} 2>&1
  if ! git merge --allow-unrelated-histories --no-commit "${sub_project}/master" >>${LOG_FILE} 2>&1 ; then
    failed "Failed to merge branch ${sub_project}/master into master"
  fi

  # And now see if we need to commit (maybe there was a merge)
  commit_merge "${sub_project}/master"

  echo
done


# Done
cd ${ROOT_DIR}
echo -e "INFO: Done."
echo -e "INFO: Done." >>${LOG_FILE} 2>&1
echo

exit 0

http://paste.ubuntu.com/11732805からも入手できます。

まず、各リポジトリへのURLを含むファイルを作成します。例:

git@github.com:eitchnet/ch.eitchnet.parent.git
git@github.com:eitchnet/ch.eitchnet.utils.git
git@github.com:eitchnet/ch.eitchnet.privilege.git

次に、プロジェクトの名前とスクリプトへのパスを指定してスクリプトを呼び出します。

./mergeGitRepositories.sh eitchnet_test eitchnet.lst

スクリプト自体には、スクリプトの機能を説明するコメントがたくさんあります。


読者を回答に誘導する代わりに、ここに回答を投稿してください(このコメントであなたが言ったことをこの回答に編集してください)。
josliber

1
確かに、自分を繰り返さない方がいいと思いました... =)
eitch

この質問が他の質問と同一であると思われる場合は、質問の下にある[フラグ]リンクを使用して重複としてフラグを付け、他の質問を示すことができます。重複する質問ではないが、まったく同じ答えを使用して両方の問題を解決できると思われる場合は、両方の問題に同じ答えを投稿してください(今行ったように)。貢献してくれてありがとう!
josliber

すごい!Windows bashプロンプトでは機能しませんでしたが、ubuntuを実行するVagrantボックスを完全に形成しました。なんと時間節約!
xverges 2017年

奉仕できて嬉しい=)
eitch

7

事実が明らかになったのは久しぶりですが、ここで見つけた他の答えに満足できなかったので、次のように書きました。

me=$(basename $0)

TMP=$(mktemp -d /tmp/$me.XXXXXXXX)
echo 
echo "building new repo in $TMP"
echo
sleep 1

set -e

cd $TMP
mkdir new-repo
cd new-repo
    git init
    cd ..

x=0
while [ -n "$1" ]; do
    repo="$1"; shift
    git clone "$repo"
    dirname=$(basename $repo | sed -e 's/\s/-/g')
    if [[ $dirname =~ ^git:.*\.git$ ]]; then
        dirname=$(echo $dirname | sed s/.git$//)
    fi

    cd $dirname
        git remote rm origin
        git filter-branch --tree-filter \
            "(mkdir -p $dirname; find . -maxdepth 1 ! -name . ! -name .git ! -name $dirname -exec mv {} $dirname/ \;)"
        cd ..

    cd new-repo
        git pull --no-commit ../$dirname
        [ $x -gt 0 ] && git commit -m "merge made by $me"
        cd ..

    x=$(( x + 1 ))
done

2
これはまさに私が探していたものでした。ありがとう!:しかし、私は、ライン22を変更しなければならなかったif [[ $dirname =~ ^.*\.git$ ]]; then
ヘイマン

2
^。* blarg $はむだに貪欲なREです。.blarg $と言って、フロントアンカーをスキップすることをお勧めします。
jettero 2013年

7

2つのリポジトリを単純に接着しようとする場合、サブモジュールとサブツリーのマージは(他の回答で指摘されているように)すべてのファイル履歴を保持しないため、使用するのに不適切なツールです。この回答を参照してくださいここでは、シンプルでこれを行うには正しい方法のために。


1
あなたの解決策は新しいリポジトリでのみうまくいきますが、ファイルの競合があるリポジトリを別のリポジトリにマージするにはどうすればよいでしょうか?
Andrey Izman 2017

6

私も同様の課題を抱えていましたが、私の場合、コードベースの1つのバージョンをリポジトリAで開発し、それを新しいリポジトリであるリポジトリBに複製して、製品の新しいバージョンを作成しました。リポジトリAのいくつかのバグを修正した後、変更をリポジトリBにFIする必要がありました。結局、次のことを行いました。

  1. リポAを指すリポBにリモートを追加する(git remote add ...)
  2. 現在のブランチをプルする(バグ修正にマスターを使用していなかった)(git pull remoteForRepoA bugFixBranch)
  3. マージをgithubにプッシュする

御馳走を働いた:)


5

@Smarに似ていますが、PRIMARYおよびSECONDARYで設定されたファイルシステムパスを使用します。

PRIMARY=~/Code/project1
SECONDARY=~/Code/project2
cd $PRIMARY
git remote add test $SECONDARY && git fetch test
git merge test/master

次に、手動でマージします。

Anar Manafovによる投稿から転載


5

2つのリポジトリをマージしています

git clone ssh://<project-repo> project1
cd project1
git remote add -f project2 project2
git merge --allow-unrelated-histories project2/master
git remote rm project2

delete the ref to avoid errors
git update-ref -d refs/remotes/project2/master

4

1つのコミットで3つ以上のプロジェクトをマージする場合は、他の回答(remote add -fmerge)で説明されている手順を実行します。次に、(ソフト)インデックスを(マージが発生しなかった)古いヘッドにリセットします。すべてのファイルを追加(git add -A)をコミットします(メッセージ「プロジェクトA、B、C、Dを1つのプロジェクトにマージしています。これはマスターのコミットIDです。

.git/info/grafts次の内容で作成します。

<commit-id of master> <list of commit ids of all parents>

を実行しますgit filter-branch -- head^..head head^2..head head^3..head。ブランチが4つ以上ある場合は、ブランチの数だけ追加head^n..headします。タグを更新するには、を追加します--tag-name-filter cat。一部のコミットが書き換えられる可能性があるため、常に追加しないでください。詳細については、filter-branchのmanページを参照して、「grafts」を検索してください

これで、最後のコミットに適切な親が関連付けられました。


1
待って、なぜ1つのコミットで3つのプロジェクトをマージしたいのですか?
スティーブベネット

リポジトリ、リポジトリクライアント、モデラーを別々のgitプロジェクトとして始めました。これは同僚にとって難しいことだったので、1つのgitプロジェクトに参加しました。新しいプロジェクトの「ルート」が他の3つのプロジェクトに由来することを可能にするために、私は単一のマージコミットを望んでいました。
koppor

4

AをB内にマージするには:

1)プロジェクトA

git fast-export --all --date-order > /tmp/ProjectAExport

2)プロジェクトB

git checkout -b projectA
git fast-import --force < /tmp/ProjectAExport

このブランチでは、実行する必要があるすべての操作を実行してコミットします。

C)次にマスターに戻り、2つのブランチ間の従来のマージ:

git checkout master
git merge projectA

2

この関数は、リモートリポジトリをローカルリポジトリのディレクトリに複製します。すべてのコミットをマージすると、保存され、git log元のコミットと適切なパスが表示されます。

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

少し変更すれば、マージされたリポジトリのファイル/ディレクトリを別のパスに移動することもできます。たとえば、次のようにします。

repo="https://github.com/example/example"
path="$(pwd)"

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

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

GIT_ADD_STORED=""

function git-mv-store
{
    from="$(echo "$1" | sed 's/\./\\./')"
    to="$(echo "$2" | sed 's/\./\\./')"

    GIT_ADD_STORED+='s,\t'"$from"',\t'"$to"',;'
}

# NOTICE! This paths used for example! Use yours instead!
git-mv-store 'public/index.php' 'public/admin.php'
git-mv-store 'public/data' 'public/x/_data'
git-mv-store 'public/.htaccess' '.htaccess'
git-mv-store 'core/config' 'config/config'
git-mv-store 'core/defines.php' 'defines/defines.php'
git-mv-store 'README.md' 'doc/README.md'
git-mv-store '.gitignore' 'unneeded/.gitignore'

git filter-branch --index-filter '
    git ls-files -s |
    sed "'"$GIT_ADD_STORED"'" |
    GIT_INDEX_FILE="$GIT_INDEX_FILE.new" git update-index --index-info &&
    mv "$GIT_INDEX_FILE.new" "$GIT_INDEX_FILE"
' HEAD

GIT_ADD_STORED=""

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"

注意事項
パスはで置き換えsedられるため、マージ後に適切なパスに移動するようにしてください。パラメータは、唯一のgit> = 2.9以降が存在します。
--allow-unrelated-histories


1

与えられたコマンドは、私が提案する最善の解決策です。

git subtree add --prefix=MY_PROJECT git://github.com/project/my_project.git master

1

プロジェクトをわずかに手動でマージします。これにより、マージの競合に対処する必要がなくなります。

まず、他のプロジェクトのファイルを好きなようにコピーします。

cp -R myotherproject newdirectory
git add newdirectory

次の歴史

git fetch path_or_url_to_other_repo

最後に取得したものの履歴にマージするようgitに指示する

echo 'FETCH_HEAD' > .git/MERGE_HEAD

今はコミットしますが、通常はコミットします

git commit

0

小さなプロジェクトを大きなプロジェクトのサブディレクトリに移動したいと思っていました。私の小さなプロジェクトには多くのコミットがなかったので、私はを使用しましたgit format-patch --output-directory /path/to/patch-dir。次に、より大きなプロジェクトでは、を使用しましたgit am --directory=dir/in/project /path/to/patch-dir/*

これは感じている方法で、フィルタ分岐未満怖いと方法がよりきれいに。もちろん、すべてのケースに当てはまるわけではありません。

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