多くのサブディレクトリを新しい個別のGitリポジトリにデタッチします


135

この質問は、別のGitリポジトリへのサブディレクトリのデタッチに基づいています

単一のサブディレクトリーを切り離す代わりに、いくつかを切り離したいと思います。たとえば、現在のディレクトリツリーは次のようになります。

/apps
  /AAA
  /BBB
  /CCC
/libs
  /XXX
  /YYY
  /ZZZ

そして、私は代わりにこれを望みます:

/apps
  /AAA
/libs
  /XXX

への--subdirectory-filter引数git filter-branchは、最初に実行されたときに指定されたディレクトリ以外のすべてを取り除くため、機能しません。--index-filterすべての不要なファイルに引数を使用するとうまくいくと思いましたが(面倒ではありますが)、複数回実行しようとすると、次のメッセージが表示されます。

Cannot create a new backup.
A previous backup already exists in refs/original/
Force overwriting the backup with -f

何か案は?TIA

回答:


155

サブシェルを扱い、ext glob(kynanが示唆するように)を使用する代わりに、次のはるかに簡単なアプローチを試してください。

git filter-branch --index-filter 'git rm --cached -qr --ignore-unmatch -- . && git reset -q $GIT_COMMIT -- apps/AAA libs/XXX' --prune-empty -- --all

で述べたようにvoid.pointer彼/彼女の中のコメント、これが以外のすべてを削除しますapps/AAAと、libs/XXX現在のリポジトリから。

空のマージコミットを整理する

これにより、多くの空のマージが残ります。これらは、ラフィネッセが彼の答えで説明したように、別のパスで削除できます:

git filter-branch --prune-empty --parent-filter \
'sed "s/-p //g" | xargs -r git show-branch --independent | sed "s/\</-p /g"'

⚠️注意:上記の必須利用GNUバージョンのsedxargsとして、それ以外の場合は、すべてのコミットを削除しますxargs失敗しました。brew install gnu-sed findutilsそしてandを使用gsedgxargsます:

git filter-branch --prune-empty --parent-filter \
'gsed "s/-p //g" | gxargs git show-branch --independent | gsed "s/\</-p /g"' 

4
さらに、-ignore-unmatchフラグをgit rmに渡す必要があります。それ以外の場合は、最初のコミットに失敗しました(私の場合、リポジトリはgit svn cloneで作成されました)
Pontomedon

8
ミックスにタグがあると仮定すると、おそらく--tag-name-filter catパラメータに追加する必要があります
Yonatan

16
この長いコマンドが何をしているかを説明する情報をもう少し追加できますか?
Burhan Ali

4
これがgit bashを使用するWindowsで完全に機能することを嬉しく思います。

3
@BurhanAli履歴のすべてのコミットで、保持したいファイルを除くすべてのファイルが削除されます。すべてが完了すると、指定したツリーの一部とその履歴のみが残ります。
void.pointer

39

シンプルなgitコマンドを使用した手動ステップ

計画は、個々のディレクトリを独自のリポジトリに分割してから、それらをマージすることです。次の手動の手順では、オタクを使用するスクリプトを使用せず、理解しやすいコマンドを使用し、余分なN個のサブフォルダーを別の単一リポジトリーにマージするのに役立ちました。

割る

元のリポジトリが次のとおりだとします:original_repo

1-アプリを分割:

git clone original_repo apps-repo
cd apps-repo
git filter-branch --prune-empty --subdirectory-filter apps master

2-分割ライブラリ

git clone original_repo libs-repo
cd libs-repo
git filter-branch --prune-empty --subdirectory-filter libs master

2つ以上のフォルダがある場合は続行します。これで、2つの新しい一時的なgitリポジトリが作成されます。

アプリとライブラリをマージして征服する

3-新しいリポジトリを準備します。

mkdir my-desired-repo
cd my-desired-repo
git init

そして、少なくとも1つのコミットを行う必要があります。次の3行をスキップする必要がある場合、最初のリポはリポのルートのすぐ下に表示されます。

touch a_file_and_make_a_commit # see user's feedback
git add a_file_and_make_a_commit
git commit -am "at least one commit is needed for it to work"

一時ファイルがコミットmergeされると、後のセクションのコマンドは期待どおりに停止します。

ユーザーのフィードバックから、のようなランダムファイルを追加する代わりに、などa_file_and_make_a_commitを追加することを選択できます。.gitignoreREADME.md

4-最初にアプリリポジトリをマージします。

git remote add apps-repo ../apps-repo
git fetch apps-repo
git merge -s ours --no-commit apps-repo/master # see below note.
git read-tree --prefix=apps -u apps-repo/master
git commit -m "import apps"

これで、新しいリポジトリー内にappsディレクトリーが表示されます。git log関連するすべての履歴コミットメッセージを表示する必要があります。

注:クリスはコメントで以下のように、gitの新しいバージョン(> = 2.9)のためには、次のように指定する必要が--allow-unrelated-historiesgit merge

5-次にlibs repoを同じ方法でマージします。

git remote add libs-repo ../libs-repo
git fetch libs-repo
git merge -s ours --no-commit libs-repo/master # see above note.
git read-tree --prefix=libs -u libs-repo/master
git commit -m "import libs"

マージするリポジトリが3つ以上ある場合は続行します。

リファレンス:gitを使用して別のリポジトリのサブディレクトリをマージする


4
git 2.9以降では、マージコマンドで--allow-unrelated-historiesを使用する必要があります。それ以外の場合、これは私にはうまく機能しているようです。
Chris

1
天才!本当にありがとうございました。非常に大きなリポジトリでツリーフィルターを使用して私が調べた最初の回答では、gitの書き換えを完了するのに26時間以上かかるとgitが予測していました。このシンプルでありながら繰り返し可能なアプローチの方がはるかに満足しており、予想されるすべてのコミット履歴を持つ4つのサブフォルダーを新しいリポジトリに正常に移動しました。
シャッティ2018年

1
最初のコミットは.gitignoreREADME.mdファイルを追加する「初期コミット」に使用できます。
Jack Miller、

2
残念ながら、このアプローチはgit merge .. git read-treeステップで追加されたファイルの追跡履歴を壊しているようです。なぜなら、それらは新しく追加されたファイルとしてそれらを記録し、私のすべてのgit guiは以前のコミットへの接続を確立しないからです。

1
@ksadjad、正直言ってわかりません。手動マージの中心は、新しいリポジトリを形成するディレクトリを選択し、コミット履歴を保持することです。コミットがファイルをdirA、dirB、dirDropに入れ、dirAとdirBのみを新しいリポジトリに選択するような状況を処理する方法がわかりません。コミット履歴が元のリポジトリとどのように関係するのか。
chfw

27

なぜfilter-branch複数回実行したいのですか?すべてを一度に実行できるため、強制する必要はありません(extglobこれを機能させるには、シェルで有効にする必要があることに注意してください)。

git filter-branch --index-filter "git rm -r -f --cached --ignore-unmatch $(ls -xd apps/!(AAA) libs/!(XXX))" --prune-empty -- --all

これにより、不要なサブディレクトリ内のすべての変更が取り除かれ、すべてのブランチとコミットが保持されます(プルーニングされたサブディレクトリ内のファイルにのみ影響する場合を除く--prune-empty)-重複したコミットなどの問題はありません。

この操作の後、不要なディレクトリはによって追跡されないものとしてリストされgit statusます。

$(ls ...)必要STでextglob使用していますあなたのシェルの代わりに、インデックスフィルタによって評価されたsh組み込みをeval(ここでextglobは利用できません)。gitでシェルオプションを有効にするにどうすればよいですか?その詳細については。


1
面白いアイデア。私は同様の問題を持っていますが、仕事にそれを得ることができなかった、見stackoverflow.com/questions/8050687/...
manol

これは、私が必要としているものとほぼ同じですが、リポジトリ全体にファイルとフォルダの両方を散りばめていました...ありがとう:)
notlesh

1
うーん。extglobをオンにしても、括弧の近くでエラーが発生します:予期しないトークンの近くに構文エラーがあります `( '私のコマンドは次のようになります:git filter-branch -f --index-filter" git rm -r -f --cached- -ignore-unmatch src / css / themes /!(some_theme *) "--prune-empty---all lsとsrc / css / themes /!(some_theme *)は、extglobが表示する他のすべてのテーマを返します動作しています...
robdodson

2
@MikeGraf私はそれが望ましい結果を与えるとは思いません:エスケープはリテラル "!"と一致します 等々。
kynan 2013年

1
@ david-smileyの(より最近の)回答は非常によく似たアプローチを使用していgitますが、コマンドのみに依存するという利点があるため、ls@ Baeが発見したように、オペレーティングシステム間での解釈方法の違いの影響を受けません。
Jeremy Caney、

20

ここで私自身の質問に答えます...多くの試行錯誤の後。

私はの組み合わせを使用して、これを行うために管理git subtreeしてgit-stitch-repo。これらの手順は以下に基づいています。

最初に、保持したいディレクトリを独自のリポジトリに取り出しました。

cd origRepo
git subtree split -P apps/AAA -b aaa
git subtree split -P libs/XXX -b xxx

cd ..
mkdir aaaRepo
cd aaaRepo
git init
git fetch ../origRepo aaa
git checkout -b master FETCH_HEAD

cd ..
mkdir xxxRepo
cd xxxRepo
git init
git fetch ../origRepo xxx
git checkout -b master FETCH_HEAD

次に、新しい空のリポジトリを作成し、最後の2つをインポート/ステッチしました。

cd ..
mkdir newRepo
cd newRepo
git init
git-stitch-repo ../aaaRepo:apps/AAA ../xxxRepo:libs/XXX | git fast-import

これは、二つのブランチを作成し、master-Aかつmaster-B、それぞれがステッチのレポの1の内容を保持しています。それらを組み合わせてクリーンアップするには:

git checkout master-A
git pull . master-B
git checkout master
git branch -d master-A 
git branch -d master-B

現在、これがどのように/いつ発生するのかはよくわかりませんが、最初checkoutとの後pullで、コードは魔法のようにマスターブランチにマージされます(ここで何が起こっているかについての洞察は高く評価されます!)

すべてが期待通りに、私は目を通す場合ことを除いて、働いているようだnewRepo歴史をコミットチェンジセットが両方の影響を受けたときに、重複があるapps/AAAlibs/XXX。重複を削除する方法があれば、それは完璧です。


ここで見つけたきちんとしたツール。「チェックアウト」に関する洞察:「git pull」は「git fetch && git merge」と同じです。「フェッチ」部分は、「ローカルでフェッチ」しているので無害です。したがって、このチェックアウトコマンドは「git merge master-B」と同じで、もう少し自明です。kernel.org/pub/software/scm/git/docs/git-pull.htmlを
10

1
残念ながら、最近の依存関係が悪いため、git-stitch-repoツールは壊れています。
Henrik

@ヘンリク正確にどのような問題が発生していましたか?export PERL5LIB="$PERL5LIB:/usr/local/git/lib/perl5/site_perl/"Git.pmが見つかるようにbash設定に追加する必要がありましたが、私には問題ありません。次に、cpanでインストールしました。

を使用git subtree addしてこのタスクを実行することができます。stackoverflow.com/a/58253979/1894803を
laconbass

7

この問題を正確に解決するためにgitフィルターを作成しました。git_filterという素晴らしい名前があり、githubの次の場所にあります。

https://github.com/slobobaby/git_filter

それは優秀なlibgit2に基づいています。

多くのコミット(〜100000)で大規模なリポジトリを分割する必要があり、git filter-branchに基づくソリューションの実行には数日かかりました。git_filterが同じことを行うのに1分かかります。


7

'git splits' git拡張を使用

git splitsjkeatingのソリューションにgit branch-filter基づいて、git拡張機能として作成したラッパーであるbashスクリプトです。

それはまさにこの状況のた​​めに作られました。エラーについては、git splits -fオプションを使用してバックアップを強制的に削除してみてください。git splitsは新しいブランチで動作するため、現在のブランチを書き換えないため、バックアップは無関係です。詳細については、Readmeを参照し、レポのコピー/クローンで使用してください(念のために!)

  1. インストールしgit splitsます。
  2. ディレクトリをローカルブランチに分割する #change into your repo's directory cd /path/to/repo #checkout the branch git checkout XYZ
    #split multiple directories into new branch XYZ git splits -b XYZ apps/AAA libs/ZZZ

  3. 空のリポジトリをどこかに作成します。xyzパスを持つGitHubで呼び出される空のリポジトリを作成したと仮定します。git@github.com:simpliwp/xyz.git

  4. 新しいレポにプッシュします。 #add a new remote origin for the empty repo so we can push to the empty repo on GitHub git remote add origin_xyz git@github.com:simpliwp/xyz.git #push the branch to the empty repo's master branch git push origin_xyz XYZ:master

  5. 新しく作成されたリモートリポジトリを新しいローカルディレクトリに複製します。
    #change current directory out of the old repo cd /path/to/where/you/want/the/new/local/repo #clone the remote repo you just pushed to git clone git@github.com:simpliwp/xyz.git


分割にファイルを追加して後で更新することはできないようですよね?
アレックス

これは、大量のコミットがある私のリポジトリで実行するのが遅いようです
Shinta Smith

git-splitはgit --indexフィルターを使用しているようです。これは--subdirectory-filterに比べて非常に遅いです。一部のリポジトリについては、それでも実行可能なオプションである可能性がありますが、大きなリポジトリ(マルチギガバイト、6桁のコミット)の場合、--index-filterは、専用のクラウドハードウェア上でも、実行に数週間かかります。
JosteinKjønigsen2018年

6
git clone git@example.com:thing.git
cd thing
git fetch
for originBranch in `git branch -r | grep -v master`; do
    branch=${originBranch:7:${#originBranch}}
    git checkout $branch
done
git checkout master

git filter-branch --index-filter 'git rm --cached -qr --ignore-unmatch -- . && git reset -q $GIT_COMMIT -- dir1 dir2 .gitignore' --prune-empty -- --all

git remote set-url origin git@example.com:newthing.git
git push --all

他のすべてのコメントを読んだことで、正しい方向に進みました。ただし、ソリューションは機能します。すべてのブランチをインポートし、複数のディレクトリで動作します!すごい!
jschober 2018

1
for他の同様の答えは、それが含まれていないので、ループは、認める価値があります。クローン内の各ブランチのローカルコピーがない場合filter-branch、書き換えの一部としてそれらを考慮しないため、他のブランチで導入されたファイルを除外する可能性がありますが、現在のブランチとまだマージされていません。(git fetch以前にチェックアウトしたブランチを最新の状態に保つためにを実行することも価値があります。)
Jeremy Caney

5

簡単な解決策:git-filter-repo

同様の問題があり、ここに記載されているさまざまなアプローチを確認した後、git-filter-repoを発見しました。こちらの公式gitドキュメントのgit-filter-branchの代替として推奨されます

既存のリポジトリのディレクトリのサブセットから新しいリポジトリを作成するには、次のコマンドを使用できます。

git filter-repo --path <file_to_remove>

複数のファイル/フォルダーをチェーンしてフィルターします。

git filter-repo --path keepthisfile --path keepthisfolder/

したがって、元の質問答えるには、git-filter-repoで次のコマンドが必要になります。

git filter-repo --path apps/AAA/ --path libs/XXX/

これは間違いなく素晴らしい答えです。他のすべてのソリューションの問題は、ディレクトリのすべてのブランチのコンテンツを抽出することができなかったことです。ただし、git filter-repoはすべてのブランチからフォルダーを取得し、履歴を完全に書き直しました。たとえば、不要なすべてのツリー全体をクリーニングするようなものです。
テオドロ

3

うん。-f後続の呼び出しでフラグを使用してバックアップを強制的に上書きし、filter-branchその警告を上書きします。:)そうでなければ、私はあなたが解決策を持っていると思います(つまり、不要なディレクトリを一度に根絶しますfilter-branch)。


-4

メッセージが示すように、refs / originalの.gitディレクトリにあるバックアップを削除します。ディレクトリは非表示です。

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