サブディレクトリのgitリポジトリをマージします


83

作業中のgitリポジトリ内のリモートgitリポジトリをそのサブディレクトリとしてマージしたいと思います。結果のリポジトリに2つのリポジトリのマージされた履歴が含まれ、マージされたリポジトリの各ファイルがリモートリポジトリにあったときと同じように履歴を保持するようにしたいと思います。サブツリーマージ戦略の使用方法で説明したようにサブツリー戦略を使用しようとしましたが、その手順に従った後、結果のリポジトリには実際に2つのリポジトリのマージ履歴が含まれていますが、リモートリポジトリからの個々のファイルは履歴を保持していません(それらのいずれかの「gitlog」は「マージされたブランチ...」というメッセージを表示するだけです)。

また、2つの結合されたgitリポジトリを分離したくないので、サブモジュールを使用したくありません。

リモートgitリポジトリをサブディレクトリとして別のリポジトリにマージし、リモートリポジトリからの個々のファイルの履歴を保持することは可能ですか?

助けてくれてありがとう。

編集:私は現在、gitfilter-branchを使用してマージされたリポジトリ履歴を書き換えるソリューションを試しています。動作しているように見えますが、もう少しテストする必要があります。調査結果の報告に戻ります。

編集2:自分自身をより明確にするために、gitのサブツリー戦略で使用した正確なコマンドを指定します。これにより、リモートリポジトリのファイルの履歴が明らかに失われます。Aを現在作業しているgitリポジトリとし、BをそのサブディレクトリとしてAに組み込みたいgitリポジトリとします。それは次のことをしました:

git remote add -f B <url-of-B>
git merge -s ours --no-commit B/master
git read-tree --prefix=subdir/Iwant/to/put/B/in/ -u B/master
git commit -m "Merge B as subdirectory in subdir/Iwant/to/put/B/in."

これらのコマンドの後、ディレクトリsubdir / Iwant / to / put / B / inに移動すると、Bのすべてのファイルが表示さgit logれますが、いずれか1つに、「Bをsubdir / Iwant / to / putのサブディレクトリとしてマージ」というコミットメッセージのみが表示されます。 /置き場。" Bのファイル履歴は失われます。

どのようだ(私は私が間違っている可能性がありますのgitの初心者ですので)仕事には次のとおりであります:

git remote add -f B <url-of-B>
git checkout -b B_branch B/master  # make a local branch following B's master
git filter-branch --index-filter \ 
   'git ls-files -s | sed "s-\t\"*-&subdir/Iwant/to/put/B/in/-" |
        GIT_INDEX_FILE=$GIT_INDEX_FILE.new \
                git update-index --index-info &&
        mv "$GIT_INDEX_FILE.new" "$GIT_INDEX_FILE"' HEAD 
git checkout master
git merge B_branch

上記のfilter-branchのコマンドはgit help filter-branch、から取得したもので、サブディレクトリパスのみを変更しました。


gitk歴史について何と言っていますか?私は過去にgitサブツリーマージをうまく使用しました。おそらくあなたはあなたの正確なコマンドを明らかにすることができますか?git-filter-branchが正しいアプローチかどうかはわかりません。git-fast-exportとgit-fast-importを試して、新しい履歴を合成することをお勧めします。
セスロバートソン

サブツリー手順を実行した後gitk、2つのリポジトリがヒントでマージされ、最初のコミットでは無関係であることが示されます。(gitkの履歴ビューのスクリーンショットを投稿すると役に立ちますか?できますか?)残念ながら、リモートリポジトリの個々のファイルは、ターミナルで保存した場合、履歴を保持していませんgit log <file-from-remote-repo>。私はに見てgit-fast-exportgit-fast-import。私はgitにとても慣れていません。質問を編集して、gitサブツリーで使用したコマンドを正確に表示します。お返事ありがとうございます。
christosc 2011年

@christosc:2番目の方法は美しく、非常に簡単に機能しました。どうもありがとうございました。subdir / Iwant / to / put / B / in /を変更して、ワンライナーにする必要がありました(Windowsのmsysgitは、コマンドでの行の戻りをサポートしていないようです):git filter-branch --index-filter'git ls-files -s | sed "s- \ t \" *-&subdir / Iwant / to / put / B / in /-"| GIT_INDEX_FILE = $ GIT_INDEX_FILE.new git update-index --index-info && mv" $ GIT_INDEX_FILE.new "" $ GIT_INDEX_FILE "'HEAD
大騒ぎ

@ user1121352お役に立ててうれしいです。
christosc 2013年

私は通常この答えに従います:stackoverflow.com/a/1684694/207791
ビクターセルジエンコ2013

回答:


37

何が起こっているのかをより完全に説明した後、私はそれを理解していると思います。いずれにせよ、一番下に回避策があります。具体的には、名前の変更の検出が--prefixを使用したサブツリーのマージによってだまされていることが起こっていると思います。これが私のテストケースです:

mkdir -p z/a z/b
cd z/a
git init
echo A>A
git add A
git commit -m A
echo AA>>A
git commit -a -m AA
cd ../b
git init
echo B>B
git add B
git commit -m B
echo BB>>B
git commit -a -m BB
cd ../a
git remote add -f B ../b
git merge -s ours --no-commit B/master
git read-tree --prefix=bdir -u B/master
git commit -m "subtree merge B into bdir"
cd bdir
echo BBB>>B
git commit -a -m BBB

それぞれいくつかのコミットでgitディレクトリaとbを作成します。サブツリーのマージを実行してから、新しいサブツリーで最終コミットを実行します。

gitk(z / aで)実行すると、履歴が表示され、表示されます。実行するgit logと、履歴が表示されます。ただし、特定のファイルを見るには問題があります。 git log bdir/B

さて、私たちがプレイできるトリックがあります。--followを使用して、特定のファイルの名前変更前の履歴を確認できます。 git log --follow -- B。これは良いことですが、マージ前の履歴とマージ後の履歴をリンクできないため、あまり良くありません。

-Mと-Cで遊んでみましたが、特定のファイルを追跡することができませんでした。

したがって、解決策は、サブツリーのマージの一部として行われる名前の変更についてgitに通知することだと思います。残念ながら、git-read-treeはサブツリーのマージについてかなり煩雑なので、一時ディレクトリを処理する必要がありますが、コミットする前にそれがなくなる可能性があります。その後、私たちは完全な歴史を見ることができます。

まず、「A」リポジトリを作成し、いくつかのコミットを行います。

mkdir -p z/a z/b
cd z/a
git init
echo A>A
git add A
git commit -m A
echo AA>>A
git commit -a -m AA

次に、「B」リポジトリを作成し、いくつかのコミットを行います。

cd ../b
git init
echo B>B
git add B
git commit -m B
echo BB>>B
git commit -a -m BB

そして、これを機能させる秘訣:サブディレクトリを作成し、その中にコンテンツを移動することで、Gitに名前の変更を認識させる。

mkdir bdir
git mv B bdir
git commit -a -m bdir-rename

リポジトリ「A」に戻り、「B」の内容をフェッチしてマージします。

cd ../a
git remote add -f B ../b
git merge -s ours --no-commit B/master
# According to Alex Brown and pjvandehaar, newer versions of git need --allow-unrelated-histories
# git merge -s ours --allow-unrelated-histories --no-commit B/master
git read-tree --prefix= -u B/master
git commit -m "subtree merge B into bdir"

それらが現在マージされていることを示すには:

cd bdir
echo BBB>>B
git commit -a -m BBB

完全な履歴が接続されたチェーンに保存されていることを証明するには:

git log --follow B

これを行った後に履歴を取得しますが、問題は、実際に古い「b」レポを保持し、時々それからマージする場合(実際にはサードパーティが個別に管理するレポであると言う)、そのサードパーティ以降に問題が発生することです。名前の変更は行われていません。新しい変更をbのバージョンに名前を変更してマージする必要がありますが、スムーズに進まないのではないかと心配しています。しかし、bがなくなると、あなたが勝ちます。


確かにそれは@Sethで動作します!また、フィルターブランチのように履歴の書き換えに頼る必要はありませんでした。これにより、多少誤解を招く履歴が作成されます(たとえば、表示中git log --stat)。また--follow、gitlogのドキュメントで切り替えに気づいていませんでした。名前を変更すると非常に便利なようです。非常に詳細で有益な返信をありがとうございました!
christosc 2011年

2
この応答は、サンプルコードが単一のセミコロンで区切られたワンライナーではなく、読み取り可能な行に分割されている場合にはるかに役立ちます。;)
jwadsa​​ck 2012

「b」を「a」にマージして、その完全な履歴を保持したいと思います。どうすればそれができますか?
emeraldhieu 2016

3
バグ修正についてはstackoverflow.com/questions/37937984/…を参照してください
Alex Brown

1
@AlexBrownが述べたように、gitこれの新しいバージョンでは生成されるfatal: refusing to merge unrelated historiesため、git merge -s ours --allow-unrelated-histories --no-commit B/master代わりに実行する必要があります。
pjvandehaar

61

git-subtreeは、履歴を保持しながら(および/またはサブツリーの履歴を分割する)複数のリポジトリを1つにマージするというまさにこのユースケース向けに設計されたスクリプトですが、これはこの質問とは無関係のようです。リリース1.7.11以降、gitツリーの一部として配布されています。

<repo>リビジョンのリポジトリを<rev>サブディレクトリとしてマージする<prefix>にはgit subtree add、次のように使用します。

git subtree add -P <prefix> <repo> <rev>

git-subtreeは、よりユーザーフレンドリーな方法でサブツリーマージ戦略を実装します。

欠点は、マージされた歴史の中のファイルは(いないサブディレクトリに)接頭辞であるということです。リポジトリマージと言うab。その結果git log a/f1、マージされた履歴内の変更を除くすべての変更(存在する場合)が表示されます。できるよ:

git log --follow -- f1

ただし、マージされた履歴以外の変更は表示されません。

つまり、aリポジトリ内ののファイルを変更しない場合は、プレフィックスなしのパスbを指定する必要があります--follow。両方のリポジトリでそれらを変更すると、2つのコマンドがあり、いずれもすべての変更を表示しません。

詳細はこちら


いいね!これはまさに私が一行で必要としていたものです。ありがとう、未来!
iameli 2015

これは、別のリポジトリをサブ方向でリポジトリにマージするのに最適なソリューションです。
eitch 2017

1
これは、の既存のサブディレクトリでは機能しないことに注意してください<prefix>。独自のリポジトリsomewhenに手動で移動されたサブディレクトリをマージするために、例えば、あなたがに戻ってそれをマージしたい。
リチャード・キーファー

6

私はしたかった

  1. 明示的なマージなしで線形履歴を維持し、
  2. マージされたリポジトリのファイルが常にサブディレクトリに存在していたように見せ、副作用として。git log -- fileなしで動作させ--followます。

ステップ1:ソースリポジトリの履歴を書き換えて、すべてのファイルが常にサブディレクトリの下に存在するように見せます。

書き換えられた履歴の一時的なブランチを作成します。

git checkout -b tmp_subdir

次にgit filter-branch「履歴を書き換えて、すでに移動したファイルを除くすべてのファイルがサブディレクトリにあるようにするにどうすればよいですか?」の説明に従って使用します。

git filter-branch --prune-empty --tree-filter '
if [ ! -e foo/bar ]; then
    mkdir -p foo/bar
    git ls-tree --name-only $GIT_COMMIT | xargs -I files mv files foo/bar
fi'

ステップ2:ターゲットリポジトリに切り替えます。ソースリポジトリをターゲットリポジトリにリモートとして追加し、そのコンテンツをフェッチします。

git remote add sourcerepo .../path/to/sourcerepo
git fetch sourcerepo

ステップ3:を使用merge --ontoして、書き換えられたソースリポジトリのコミットをターゲットリポジトリの上に追加します。

git rebase --preserve-merges --onto master --root sourcerepo/tmp_subdir

ログをチェックして、これで本当に必要なものが得られたことを確認できます。

git log --stat

ステップ4:リベース後、「切り離されたHEAD」状態になります。マスターを新しいヘッドに早送りできます。

git checkout -b tmp_merged
git checkout master
git merge tmp_merged
git branch -d tmp_merged

ステップ5:最後にいくつかのクリーンアップ:一時的なリモートを削除します。

git remote rm sourcerepo

git rebase指定されたオプションを一緒に許可していないようです: "エラー:インタラクティブオプションを組み合わせることができません(--interactive、-exec、-rebase-merges、-preserve-merges、-keep-empty、-root +- -onto)amオプション付き(--committer-date-is-author-date) "
Sam

面白い!ドロップしてみてください--committer-date-is-author-date。互換性のないオプションのチェックは、最近git v2.19.0(github.com/git/git/commit/…)で追加されました。説明から、--committer-date-is-author-dateとにかく前に黙って無視されたかのように聞こえます。
HFS

古いfilter-branchコマンドを使用するのではなく、を使用してくださいgit filter-repo --to-subdirectory-filter <dir>。はるかに高速で簡単です。
ウィレム

5

あなたが本当に物事をつなぎ合わせたいのなら、接ぎ木を調べてください。また、を使用する必要がありますgit rebase --preserve-merges --onto。コミッター情報の作成者の日付を保持するオプションもあります。


@adymitrukお返事ありがとうございます。私はgitに本当に慣れていないので、あなたが提案するソリューションを調べます。試してみましたがgit filter-branch、うまくいくようですが、おそらくあなたの方が良いでしょう。やってみます。
christosc 2011年

@adymitrukブランチとして相互に関連していない2つのリポジトリでリベースを使用できますか?つまり、マージしたい2つのリポジトリには、共通の初期コミットがありません...
christosc 2011年

@adymitrukに感謝します。無関係な2つのリポジトリでリベースを実行できるかどうかはわかりませんでした。それは確かに役立つでしょう…
christosc 2011年

しかし、フィルターブランチを恐れないでください。それは私たちを何度も救ってくれました。事前に別のブランチを作成するだけで、いつでも戻ることができます。それ、またはreflogを使用します。
Adam Dymitruk 2011年

なるほど…とにかく、これらのgitの概念とコマンドに関するドキュメントを読んだほうがいいでしょう。VCS、つまりsvnの経験はほとんどないので、私はgitにちょっと圧倒されています。しかし、その力はそれだけの価値があるようです。
christosc 2011年

4

次の解決策が私にとって有効であることがわかりました。まず、プロジェクトBに移動し、すべてのファイルが新しいサブディレクトリに移動される新しいブランチを作成します。次に、この新しいブランチを元にプッシュします。次に、プロジェクトAに移動し、Bのリモートを追加してフェッチし、移動したブランチをチェックアウトし、マスターに戻ってマージします。

# in local copy of project B
git checkout -b prepare_move
mkdir subdir
git mv <files_to_move> subdir/
git commit -m 'move files to subdir'
git push origin prepare_move

# in local copy of project A
git remote add -f B_origin <remote-url>
git checkout -b from_B B_origin/prepare_move
git checkout master
git merge from_B

サブディレクトリに移動subdirするgit log --followと、履歴を使用して保持できます。

私はgitの専門家ではないので、これが特に優れた解決策であるかどうか、または警告があるかどうかについてはコメントできませんが、これまでのところ、すべて問題ないようです。


人々はここに、このアプローチをupvotingているように見える:stackoverflow.com/questions/1683531/...
nacross

3

追加のリポジトリをgitサブモジュールとして追加しようとしましたか?履歴を含まれているリポジトリとマージすることはありません。実際、独立したリポジトリになります。

あなたがそうしなかったので、私はそれについて言及します。


1
答えAbizernをありがとう。実際、2つのリポジトリ履歴を1つにマージしたいと思います。もう別々にしたくないので、サブモジュールについては触れませんでした。
christosc 2011年

0

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

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

このためにはgit-filter-repoインストールする必要があります(お勧めしません)。filter-branch

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

詳細はこちら

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