サブディレクトリを別のGitリポジトリにデタッチ(移動)する


1758

私が持っているGitのサブディレクトリの数を含むリポジトリを。ここで、サブディレクトリの1つが他のサブディレクトリとは無関係であり、別のリポジトリに分離する必要があることがわかりました。

サブディレクトリ内のファイルの履歴を保持しながらこれを行うにはどうすればよいですか?

クローンを作成して、各クローンの不要な部分を削除することはできると思いますが、これにより、古いリビジョンなどをチェックアウトしたときに完全なツリーが得られると思います。これは許容できるかもしれませんが、 2つのリポジトリには共有履歴がありません。

明確にするために、私は次のような構造になっています。

XYZ/
    .git/
    XY1/
    ABC/
    XY2/

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

XYZ/
    .git/
    XY1/
    XY2/
ABC/
    .git/
    ABC/

7
これはささいなgit filter-branchことですが、以下の私の回答を参照してください。
jeremyjjbrown 2014

8
@jeremyjjbrownは正しいです。これは難しいことではありませんが、古い答えがすべて結果を左右するため、Googleで正しい答えを見つけるのは困難です。
Agnel Kurian 2014年

回答:


1228

更新:このプロセスは非常に一般的であるため、Gitチームは新しいツールを使用してプロセスを大幅に簡素化しましたgit subtree。ここを参照:サブディレクトリを個別のGitリポジトリにデタッチ(移動)します。


リポジトリのクローンを作成し、それを使用git filter-branchして、新しいリポジトリ内のサブディレクトリ以外のすべてにガベージコレクションの対象としてマークを付けます。

  1. ローカルリポジトリのクローンを作成するには:

    git clone /XYZ /ABC
    

    (注:リポジトリはハードリンクを使用して複製されますが、ハードリンクファイル自体は変更されないため、問題ありません。新しいファイルが作成されます。)

  2. 次に、書き直したい興味深いブランチを保持し、オリジンを削除して、そこにプッシュされないようにし、古いコミットがオリジンによって参照されないようにします。

    cd /ABC
    for i in branch1 br2 br3; do git branch -t $i origin/$i; done
    git remote rm origin
    

    またはすべてのリモートブランチ:

    cd /ABC
    for i in $(git branch -r | sed "s/.*origin\///"); do git branch -t $i origin/$i; done
    git remote rm origin
    
  3. 次に、サブプロジェクトと関係のないタグも削除したい場合があります。後でそれを行うこともできますが、リポジトリを再度プルーニングする必要がある場合があります。私はそうしなかったので、WARNING: Ref 'refs/tags/v0.1' is unchangedすべてのタグのを取得しました(これらはすべてサブプロジェクトとは無関係だったため)。さらに、そのようなタグを削除した後、より多くのスペースが再利用されます。どうやらgit filter-branch他のタグを書き換えることができるはずですが、これを確認できませんでした。すべてのタグを削除する場合は、を使用しますgit tag -l | xargs git tag -d

  4. 次に、filter-branchとresetを使用して他のファイルを除外し、プルーニングできるようにします。--tag-name-filter cat --prune-empty空のコミットを削除し、タグを書き換えるために追加してみましょう(これは、署名を取り除く必要があることに注意してください):

    git filter-branch --tag-name-filter cat --prune-empty --subdirectory-filter ABC -- --all
    

    または、HEADブランチのみを書き換え、タグやその他のブランチを無視するには、次のようにします。

    git filter-branch --tag-name-filter cat --prune-empty --subdirectory-filter ABC HEAD
    
  5. 次に、バックアップreflogを削除して、スペースを本当に再利用できるようにします(ただし、操作は破壊的です)。

    git reset --hard
    git for-each-ref --format="%(refname)" refs/original/ | xargs -n 1 git update-ref -d
    git reflog expire --expire=now --all
    git gc --aggressive --prune=now
    

    これで、すべての履歴が保存されたABCサブディレクトリのローカルgitリポジトリが作成されました。

注:ほとんどの用途でgit filter-branchは、実際にパラメータが追加されているはず-- --allです。はい、そう--space-- allです。これは、コマンドの最後のパラメーターである必要があります。Matliが発見したように、これにより、プロジェクトのブランチとタグが新しいリポジトリに含まれたままになります。

編集:たとえば、リポジトリが実際に縮小されることを確認するために、以下のコメントからのさまざまな提案が組み込まれました(以前は常にそうだったわけではありませんでした)。


29
とても良い答えです。ありがとう!そして、私が本当に望んでいたものを正確に取得するために、filter-branchコマンドに "---all"を追加しました。
matli 2008

12
なぜ必要なの--no-hardlinksですか?1つのハードリンクを削除しても、他のファイルには影響しません。Gitオブジェクトも不変です。必要な所有者/ファイルの権限を変更する場合のみ--no-hardlinks
vdboor

67
私がお勧めする追加のステップは、「git remote rm origin」です。これにより、私が間違っていない限り、プッシュが元のリポジトリに戻るのを防ぐことができます。
トム

13
追加するもう1つのコマンドfilter-branch--prune-empty、現在空のコミットを削除することです。
セスジョンソン

8
Paulと同様に、新しいリポジトリにプロジェクトタグが必要なかったため、を使用しませんでした-- --all。私はまた走っgit remote rm originて、git tag -l | xargs git tag -dgit filter-branchのコマンド。これにより、.gitディレクトリが60Mから〜300Kに縮小されました。サイズを小さくするには、これらのコマンドを両方とも実行する必要があることに注意してください。
saltycrane

1321

Easy Way™

これは非常に一般的で有用な慣行であり、Gitのオーバーロードによって本当に簡単になりましたが、新しいバージョンのGit(> = 1.7.11 May 2012)が必要です。最新のGitのインストール方法については、付録を参照してください。また、以下のウォークスルーには実際の例があります。

  1. 古いレポを準備する

    cd <big-repo>
    git subtree split -P <name-of-folder> -b <name-of-new-branch>
    

    注: <name-of-folder>先頭または末尾の文字を含めることはできません。たとえば、という名前のフォルダがsubprojectとして渡さなければならsubprojectないで、./subproject/

    Windowsユーザーへの注意:フォルダーの深さが1 <name-of-folder>より大きい場合、* nixスタイルのフォルダー区切り記号(/)が必要です。たとえば、名前の付いたフォルダpath1\path2\subprojectは、path1/path2/subproject

  2. 新しいレポを作成する

    mkdir ~/<new-repo> && cd ~/<new-repo>
    git init
    git pull </path/to/big-repo> <name-of-new-branch>
    
  3. 新しいリポジトリをGitHubまたはどこにでもリンクする

    git remote add origin <git@github.com:user/new-repo.git>
    git push -u origin master
    
  4. 必要に応じ<big-repo>、内部のクリーンアップ

    git rm -rf <name-of-folder>
    

    :これにより、すべての履歴参照がリポジトリに残ります。実際にパスワードのコミットを心配している場合、または.gitフォルダのファイルサイズを小さくする必要がある場合は、以下の付録を参照してください。

...

ウォークスルー

これらは上記と同じ手順ですが、使用する代わりに私のリポジトリの正確な手順に従います<meta-named-things>

以下は、ノードにJavaScriptブラウザーモジュールを実装するためのプロジェクトです。

tree ~/node-browser-compat

node-browser-compat
├── ArrayBuffer
├── Audio
├── Blob
├── FormData
├── atob
├── btoa
├── location
└── navigator

単一のフォルダーをbtoa別のGitリポジトリに分割したい

cd ~/node-browser-compat/
git subtree split -P btoa -b btoa-only

これでbtoa-only、コミットのみが含まれる新しいブランチがあり、btoa新しいリポジトリを作成したいと思います。

mkdir ~/btoa/ && cd ~/btoa/
git init
git pull ~/node-browser-compat btoa-only

次に、GitHubまたはBitbucketなどに新しいリポジトリを作成し、それを origin

git remote add origin git@github.com:node-browser-compat/btoa.git
git push -u origin master

幸せな日!

注意:とでリポジトリを作成した場合はREADME.md、最初にプルする必要があります。.gitignoreLICENSE

git pull origin master
git push origin master

最後に、大きなリポジトリからフォルダを削除します

git rm -rf btoa

...

付録

macOSの最新のGit

Homebrewを使用して最新バージョンのGitを取得するには:

brew install git

Ubuntuの最新Git

sudo apt-get update
sudo apt-get install git
git --version

それが機能しない場合(非常に古いバージョンのUbuntuを使用している場合)、

sudo add-apt-repository ppa:git-core/ppa
sudo apt-get update
sudo apt-get install git

それでもうまくいかない場合は、

sudo chmod +x /usr/share/doc/git/contrib/subtree/git-subtree.sh
sudo ln -s \
/usr/share/doc/git/contrib/subtree/git-subtree.sh \
/usr/lib/git-core/git-subtree

コメントからrui.araujoに感謝します。

履歴をクリアする

デフォルトでは、Gitからファイルを削除しても実際には削除されません。ファイルが存在しないことをコミットするだけです。実際に履歴参照を削除したい場合(つまり、パスワードをコミットした場合)、これを行う必要があります。

git filter-branch --prune-empty --tree-filter 'rm -rf <name-of-folder>' HEAD

その後、ファイルまたはフォルダーがGit履歴に表示されなくなったことを確認できます

git log -- <name-of-folder> # should show nothing

ただし、GitHubなどに削除を「プッシュ」することはできません。あなたがしようとすると、エラーが発生し、あなたがgit pullできる前にそうしなければならないでしょうgit push、履歴にすべてが戻ってきます。

したがって、「origin」から履歴を削除する場合、つまりGitHub、Bitbucketなどから履歴を削除する場合は、リポジトリを削除して、削除されたリポジトリのコピーを再度プッシュする必要があります。しかし、待ってください- さらにあります!-パスワードまたはそのようなものを取り除くことについて本当に心配している場合は、バックアップを削除する必要があります(以下を参照)。

作る.git小さな

前述の履歴の削除コマンドでは、バックアップファイルがたくさん残されています。Gitは、誤ってリポジトリを壊さないようにするのにとても親切だからです。孤立したファイルは最終的に数日から数か月にわたって削除されますが、不要なファイルを誤って削除してしまったことに気付いた場合に備えて、しばらく放置されます。

ですから、本当にゴミ箱にしてレポのクローンサイズをすぐに減らしたい場合は、次の本当に奇妙なことをすべて実行する必要があります。

rm -rf .git/refs/original/ && \
git reflog expire --all && \
git gc --aggressive --prune=now

git reflog expire --all --expire-unreachable=0
git repack -A -d
git prune

とはいえ、必要なことがわかっている場合を除いて、これらの手順を実行しないことをお勧めします-間違ったサブディレクトリを削除した場合に備えて、知っていますか?バックアップファイルは、レポをプッシュするときに複製されるべきではありません。ローカルコピーにあるだけです。

クレジット


16
git subtreeはまだ「contrib」フォルダの一部であり、デフォルトではすべてのディストリビューションにインストールされていません。 github.com/git/git/blob/master/contrib/subtree
onionjake

11
@krlmlr sudo chmod + x /usr/share/doc/git/contrib/subtree/git-subtree.sh sudo ln -s /usr/share/doc/git/contrib/subtree/git-subtree.sh / usr / lib / git-core / git-subtree Ubuntu 13.04でアクティブ化するには
rui.araujo 2013

41
パスワードを公開リポジトリにプッシュした場合は、パスワードを変更する必要があります。公開リポジトリから削除しないでください。誰にも見られないようにしてください。
Miles Rout 2013

8
このソリューションは履歴を保持しません。
・クール

18
コマンドそれが何を意図するもの完全に理解するために、これはかなりの暗黙的な作りと難しい...popdpushd
jones77

133

Paulの答えは、/ ABCを含む新しいリポジトリを作成しますが、/ XYZ内から/ ABCを削除しません。次のコマンドは、/ XYZ内から/ ABCを削除します。

git filter-branch --tree-filter "rm -rf ABC" --prune-empty HEAD

もちろん、最初に 'clone --no-hardlinks'リポジトリでテストし、Paulがリストするreset、gc、およびpruneコマンドを実行してください。


53
git filter-branch --index-filter "git rm -r -f --cached --ignore-unmatch ABC" --prune-empty HEADそれを作ると 、それははるかに速くなります。index-filterはインデックスで機能しますが、tree-filterはすべてのcommitについてすべてをチェックアウトしてステージングする必要があります
fmarc 2009

51
場合によっては、リポジトリXYZの履歴を台無しにするのはやりすぎです...単純な "rm -rf ABC; git rm -r ABC; git commit -m'extracted ABC into your own repo '"は、ほとんどの人にとってよりうまく機能します。
Evgeny

2
たとえば、2つのディレクトリを分離した後で削除する場合など、このコマンドで-f(強制)を2回以上使用することをお勧めします。それ以外の場合は、「新しいバックアップを作成できません」と表示されます。
ブライアンカールトン

4
あなたがやっている場合は--index-filterこの方法を、あなたもそれを加えることができますgit rm -q -r -f各呼び出しは、それが削除され、各ファイルの行を印刷しないように、。
Eric Naeseth、2011年

1
ポールの答えが非常に徹底しているという理由だけで、ポールの答えを編集することをお勧めします。
Erik Aronesty 2014年

96

新しいリポジトリから古い履歴を適切に削除するには、filter-branch手順の後にもう少し作業を行う必要があることがわかりました。

  1. クローンとフィルターを実行します。

    git clone --no-hardlinks foo bar; cd bar
    git filter-branch --subdirectory-filter subdir/you/want
    
  2. 古い履歴へのすべての参照を削除します。「origin」はクローンを追跡し、「original」はフィルターブランチが古いものを保存する場所です。

    git remote rm origin
    git update-ref -d refs/original/refs/heads/master
    git reflog expire --expire=now --all
    
  3. 今でも、fsckが触れないpackfileに履歴が残っている可能性があります。細断して、新しいpackfileを作成し、未使用のオブジェクトを削除します。

    git repack -ad
    

あり、この説明では、フィルタ分岐のためのマニュアルが


3
git gc --aggressive --prune=nowまだ何か足りないのではないかと思いますね。
アルバート

1
@Albert repackコマンドがそれを処理し、緩いオブジェクトはありません。
Josh Lee

ええ、git gc --aggressive --prune=now新しいレポの多くを減らしました
Tomek Wyderka 2013

シンプルでエレガント。ありがとう!
マルコペレグリーニ

40

編集:Bashスクリプトが追加されました。

ここで与えられた答えは、私にとっては部分的にうまくいきました。大きなファイルがたくさんキャッシュに残りました。最終的に機能したもの(freenodeの#gitで数時間後):

git clone --no-hardlinks file:///SOURCE /tmp/blubb
cd blubb
git filter-branch --subdirectory-filter ./PATH_TO_EXTRACT  --prune-empty --tag-name-filter cat -- --all
git clone file:///tmp/blubb/ /tmp/blooh
cd /tmp/blooh
git reflog expire --expire=now --all
git repack -ad
git gc --prune=now

以前のソリューションでは、リポジトリのサイズは約100 MBでした。これにより1.7 MBになりました。多分それは誰かを助ける:)


次のbashスクリプトは、タスクを自動化します。

!/bin/bash

if (( $# < 3 ))
then
    echo "Usage:   $0 </path/to/repo/> <directory/to/extract/> <newName>"
    echo
    echo "Example: $0 /Projects/42.git first/answer/ firstAnswer"
    exit 1
fi


clone=/tmp/${3}Clone
newN=/tmp/${3}

git clone --no-hardlinks file://$1 ${clone}
cd ${clone}

git filter-branch --subdirectory-filter $2  --prune-empty --tag-name-filter cat -- --all

git clone file://${clone} ${newN}
cd ${newN}

git reflog expire --expire=now --all
git repack -ad
git gc --prune=now

26

これはそれほど複雑ではなくなったので、リポジトリのクローンでgit filter-branchコマンドを使用して、不要なサブディレクトリをカリングし、新しいリモートにプッシュすることができます。

git filter-branch --prune-empty --subdirectory-filter <YOUR_SUBDIR_TO_KEEP> master
git push <MY_NEW_REMOTE_URL> -f .

3
これは魅力のように働きました。上記の例のYOUR_SUBDIRは、保持したいサブディレクトリです。その他はすべて削除されます
JT Taylor

1
コメントに基づく更新。
jeremyjjbrown 2015

2
これは質問の答えにはなりません。それは言うドキュメントから、The result will contain that directory (and only that) as its project root.そして確かにこれはあなたが得るものです、すなわち元のプロジェクト構造は保存されません。
NicBright 2017年

2
@NicBright問題のXYZとABCの問題を説明して、何が問題なのかを示していただけますか
アダム

@jeremyjjbrownそれがクローン化されたレポを再利用し、新しいレポここでは、すなわち私の質問を使用しないことが可能であるstackoverflow.com/questions/49269602/...
Qiulang

19

更新:git-subtreeモジュールは非常に有用だったので、gitチームはそれをコアに組み込んで作成しましたgit subtree。ここを参照してください:サブディレクトリを別のGitリポジトリにデタッチ(移動)します。

git-subtreeはこれに役立つかもしれません

http://github.com/apenwarr/git-subtree/blob/master/git-subtree.txt(非推奨)

http://psionides.jogger.pl/2010/02/04/sharing-code-between-projects-with-git-subtree/


1
git-subtreeはGitの一部になりましたが、contribツリーにあるため、デフォルトで常にインストールされるわけではありません。私はそれがHomebrew git式によってインストールされていることを知っていますが、そのmanページはありません。したがって、apenwarrは彼のバージョンを時代遅れだと呼びます。
echristopherson 2013年

19

複数のサブフォルダを分割するために、CoolAJ86「The Easy Way™」の解答を少し変更します(たとえばsub1sub2、新たなgitリポジトリに)は。

Easy Way™(複数のサブフォルダー)

  1. 古いレポを準備する

    pushd <big-repo>
    git filter-branch --tree-filter "mkdir <name-of-folder>; mv <sub1> <sub2> <name-of-folder>/" HEAD
    git subtree split -P <name-of-folder> -b <name-of-new-branch>
    popd
    

    注: <name-of-folder>先頭または末尾の文字を含めることはできません。たとえば、という名前のフォルダがsubprojectとして渡さなければならsubprojectないで、./subproject/

    Windowsユーザーへの注意:フォルダーの深さが> 1の場合、<name-of-folder>* nixスタイルのフォルダー区切り記号(/)が必要です。たとえば、名前の付いたフォルダpath1\path2\subprojectはとして渡される必要がありますpath1/path2/subproject。また、mvコマンドは使わないでくださいmove

    最後の注意:基本的な回答とのユニークで大きな違いは、スクリプト " git filter-branch..."の2行目です。

  2. 新しいレポを作成する

    mkdir <new-repo>
    pushd <new-repo>
    
    git init
    git pull </path/to/big-repo> <name-of-new-branch>
    
  3. 新しいリポジトリをGithubまたはどこにでもリンクする

    git remote add origin <git@github.com:my-user/new-repo.git>
    git push origin -u master
    
  4. 必要に応じてクリーンアップ

    popd # get out of <new-repo>
    pushd <big-repo>
    
    git rm -rf <name-of-folder>
    

    :これにより、すべての履歴参照がリポジトリに残ります。実際にパスワードをコミットしたかどうか、または.gitフォルダのファイルサイズを小さくする必要がある場合は、元の回答の付録を参照してください。


1
これは私にわずかな変更を加えてうまくいきました。私sub1sub2フォルダは初期バージョンでは存在しなかったため、--tree-filter次のようにスクリプトを変更する必要がありました"mkdir <name-of-folder>; if [ -d sub1 ]; then mv <sub1> <name-of-folder>/; fi"。2番目のfilter-branchコマンドでは、<sub1>を<sub2>に置き換え、<name-of-folder>の作成を省略し、既存のバックアップの警告を上書きするために-ffilter-branchに含めました。
pglezen

これは、gitの履歴中にサブディレクトリのいずれかが変更された場合は機能しません。これをどのように解決できますか?
nietras 2016

@nietrasはrogerdpackの回答を参照してください。これらの他の回答のすべての情報を読んで吸収した後、それを見つけるのにしばらく時間がかかりました。
アダム

12

元の質問では、XYZ / ABC /(* files)がABC / ABC /(* files)になることを望んでいます。自分のコードに受け入れられた回答を実装した後、それが実際にXYZ / ABC /(* files)をABC /(* files)に変更することに気付きました。フィルターブランチのマニュアルページには、

結果には、プロジェクトルートとしてそのディレクトリ(およびそのディレクトリのみ)が含まれます。

つまり、最上位のフォルダを1つ上のレベルに昇格させます。これは重要な違いです。たとえば、私の履歴では、最上位のフォルダーの名前を変更したためです。フォルダを1レベル上に昇格させることで、gitは、名前を変更したコミットでの連続性を失います。

フィルターブランチ後に連続性が失われた

質問に対する私の答えは、リポジトリの2つのコピーを作成し、それぞれに保持するフォルダを手動で削除することです。manページはこれで私をバックアップします:

[...]単純な単一のコミットで問題を解決するのに十分である場合は、[このコマンド]の使用を避けます


1
そのグラフのスタイルが好きです。使用しているツールを教えてください。
Slipp D. Thompson 2013

3
Mac用タワー。私は本当にそれが好き。それ自体でMacに切り替える価値はほとんどありません。
MM。

2
そうです、私の場合、私のサブフォルダtargetdirはある時点で名前変更されており、git filter-branch単にそれを1日と呼び、名前を変更する前に行われたすべてのコミットを削除しています!驚くべきことに、Gitがそのようなことを追跡し、個々のコンテンツのチャンクを移行することについて熟練したGitを考えると!
ジェイアレン

1
ああ、また、誰かが同じボートにいるのを見つけた場合、ここで私が使用したコマンドを示します。それは忘れないでくださいgit rm、複数の引数を取りますので、それぞれのファイル/フォルダのためにそれを実行する理由はありません: BYEBYE="dir/subdir2 dir2 file1 dir/file2"; git filter-branch -f --index-filter "git rm -q -r -f --cached --ignore-unmatch $BYEBYE" --prune-empty -- --all
ジェイ・アレン

7

ポールの答えに追加するにはには、スペースを最終的に回復するには、HEADをクリーンなリポジトリにプッシュする必要があり、.git / objects / packディレクトリのサイズを小さくする必要があることがわかりました。

すなわち

$ mkdir ... ABC.git
$ cd ... ABC.git
$ git init --bare

GCプルーンの後に、以下も実行します。

$ git push ... ABC.git HEAD

その後、行うことができます

$ git clone ... ABC.git

ABC / .gitのサイズが縮小されます

実際、時間のかかるいくつかのステップ(たとえばgit gc)は、リポジトリをクリーンアップするためのプッシュでは必要ありません。

$ git clone --no-hardlinks / XYZ / ABC
$ git filter-branch --subdirectory-filter ABC HEAD
$ git reset --hard
$ git push ... ABC.git HEAD

6

今の正しい方法は次のとおりです:

git filter-branch --prune-empty --subdirectory-filter FOLDER_NAME [first_branch] [another_branch]

GitHubには、そのようなケースに関する小さな記事さえあります。

ただし、最初に元のリポジトリを別のディレクトリに複製してください(すべてのファイルと他のディレクトリが削除され、おそらくそれらを操作する必要があるため)。

したがって、アルゴリズムは次のようになります。

  1. リモートリポジトリを別のディレクトリに複製する
  2. git filter-branch一部のサブディレクトリの下にある左のみのファイルを使用して、新しいリモートにプッシュする
  3. 元のリモートリポジトリからこのサブディレクトリを削除するコミットを作成します

6

ここでの回答のほとんど(すべて?)は、何らかの形式git filter-branch --subdirectory-filterとその同類に依存しているようです。これは「ほとんどの場合」機能する可能性がありますが、たとえばフォルダの名前を変更した場合など、一部のケースでは次のようになります。

 ABC/
    /move_this_dir # did some work here, then renamed it to

ABC/
    /move_this_dir_renamed

通常のgitフィルタースタイルを使用して「move_me_renamed」を抽出すると、最初にmove_this_dir(ref)であったときに後ろから発生したファイル変更履歴が失われます。

したがって、すべての変更履歴を実際に保持する唯一の方法(これがこのようなケースの場合)は、本質的には、リポジトリをコピー(新しいリポジトリを作成し、それを起点に設定)してから、他のすべてのものをnukeすることです次のようにサブディレクトリの名前を親に変更します。

  1. ローカルでマルチモジュールプロジェクトのクローンを作成する
  2. ブランチ-何があるか確認してください: git branch -a
  3. ワークステーションにローカルコピーを取得するには、スプリットに含まれる各ブランチをチェックアウトします。 git checkout --track origin/branchABC
  4. 新しいディレクトリにコピーを作成します。 cp -r oldmultimod simple
  5. 新しいプロジェクトのコピーに移動します。 cd simple
  6. このプロジェクトで不要な他のモジュールを削除します。
  7. git rm otherModule1 other2 other3
  8. これで、ターゲットモジュールのサブディレクトリのみが残ります
  9. モジュールルートが新しいプロジェクトルートになるように、モジュールサブディレクトリを削除します。
  10. git mv moduleSubdir1/* .
  11. relicサブディレクトリを削除します。 rmdir moduleSubdir1
  12. いつでも変更を確認します。 git status
  13. 新しいgitリポジトリを作成し、そのURLをコピーして、このプロジェクトを指すようにします。
  14. git remote set-url origin http://mygithost:8080/git/our-splitted-module-repo
  15. これが正しいことを確認します。 git remote -v
  16. 変更をリモートリポジトリにプッシュします。 git push
  17. リモートリポジトリに移動し、それがすべてあることを確認します
  18. 必要な他のブランチについても同じ手順を繰り返します。 git checkout branch2

これはgithub docの「サブフォルダーを新しいリポジトリに分割する」手順6〜11 に従ってモジュールを新しいリポジトリにプッシュします。

これにより、.gitフォルダーのスペースが節約されることはありませんが、名前を変更しても、それらのファイルの変更履歴はすべて保持されます。そして、「たくさんの」履歴が失われていなければ、これは価値がないかもしれません。しかし、少なくとも古いコミットを失わないことが保証されています!


1
git haystackで針を見つけました!これで、すべてのコミット履歴を保持できます。
アダム

5

サブフォルダを新しいリポジトリに分割するためのGitHubのガイドをお勧めします。手順はPaulの回答と似ていますが、手順がわかりやすいと思いました。

手順を変更して、GitHubでホストされているリポジトリではなく、ローカルリポジトリに適用するようにしました。


サブフォルダーを新しいリポジトリーに分割する

  1. Git Bashを開きます。

  2. 現在の作業ディレクトリを、新しいリポジトリを作成する場所に変更します。

  3. サブフォルダーを含むリポジトリのクローンを作成します。

git clone OLD-REPOSITORY-FOLDER NEW-REPOSITORY-FOLDER
  1. 現在の作業ディレクトリをクローンしたリポジトリに変更します。

cd REPOSITORY-NAME
  1. リポジトリ内の残りのファイルからサブフォルダを除外するには、次を実行します。 git filter-branch、次の情報を提供します。
    • FOLDER-NAME:別のリポジトリを作成するプロジェクト内のフォルダー。
      • ヒント:Windowsユーザーは、/フォルダーの区切りにを使用する必要があります。
    • BRANCH-NAME:現在のプロジェクトのデフォルトのブランチ(例:masterまたは)gh-pages

git filter-branch --prune-empty --subdirectory-filter FOLDER-NAME  BRANCH-NAME 
# Filter the specified branch in your directory and remove empty commits
Rewrite 48dc599c80e20527ed902928085e7861e6b3cbe6 (89/89)
Ref 'refs/heads/BRANCH-NAME' was rewritten

素敵な投稿ですが、リンクしたドキュメントの最初の段落に「If you create a new clone of the repository, you won't lose any of your Git history or changes when you split a folder into a separate repository.すべての回答に対するコメントによると両方ともここに記載されています」filter-branchとあり、subtreeスクリプトによってサブディレクトリの名前が変更されていると、履歴が失われます。これに対処するためにできることはありますか?
アダム

先行するディレクトリの名前変更/移動を含む、すべてのコミットを保持するためのソリューションが見つかりました-これはまさにこの質問に対するrogerdpackの答えです。
アダム

唯一の問題は、クローンリポジトリをもう使用できないことです
Qiulang

5

git filter-branch新しいバージョンgit2.22+多分?)を使用して実行すると、この新しいツールgit-filter-repoを使用するように言われます。このツールは確かに私にとって物事を簡素化しました。

filter-repoによるフィルタリング

XYZ元の質問からリポジトリを作成するコマンド:

# create local clone of original repo in directory XYZ
tmp $ git clone git@github.com:user/original.git XYZ

# switch to working in XYZ
tmp $ cd XYZ

# keep subdirectories XY1 and XY2 (dropping ABC)
XYZ $ git filter-repo --path XY1 --path XY2

# note: original remote origin was dropped
# (protecting against accidental pushes overwriting original repo data)

# XYZ $ ls -1
# XY1
# XY2

# XYZ $ git log --oneline
# last commit modifying ./XY1 or ./XY2
# first commit modifying ./XY1 or ./XY2

# point at new hosted, dedicated repo
XYZ $ git remote add origin git@github.com:user/XYZ.git

# push (and track) remote master
XYZ $ git push -u origin master

仮定: *リモートXYZリポジトリは、プッシュ前は新しく空でした

フィルタリングと移動

私の場合、より一貫した構造にするために、いくつかのディレクトリを移動したいと思っていました。最初はその単純なfilter-repoコマンドの後にを実行しgit mv dir-to-renameましたが、この--path-renameオプションを使用すると、少し「より良い」履歴を取得できることがわかりました。5 hours ago移動したファイルで最後に変更されたファイルを新しいリポジトリで表示する代わりに、last year(GitHub UIで)表示されます。これは、元のリポジトリでの変更時間と一致します。

の代わりに...

git filter-repo --path XY1 --path XY2 --path inconsistent
git mv inconsistent XY3  # which updates last modification time

私は最終的に走った...

git filter-repo --path XY1 --path XY2 --path inconsistent --path-rename inconsistent:XY3
ノート:
  • Git Rev Newsブログの投稿で、さらに別のリポジトリフィルタリングツールを作成する理由がよく説明されていると思いました。
  • 最初に、元のリポジトリでターゲットリポジトリ名と一致するサブディレクトリを作成し、次に(を使用してgit filter-repo --subdirectory-filter dir-matching-new-repo-name)フィルタリングするパスを試しました。このコマンドは、そのサブディレクトリをコピーされたローカルリポジトリのルートに正しく変換しましたが、サブディレクトリの作成にかかった3つのコミットのみの履歴ももたらしました。(これが--path複数回指定できることに気づかなかったため、ソースリポジトリにサブディレクトリを作成する必要がなくなりました。)誰かがソースリポジトリにコミットしていたので、履歴、私git reset commit-before-subdir-move --hardcloneコマンドの後に使用し、わずかに変更されたローカルクローンで動作するようにコマンドに追加--forceしましたfilter-repo
git clone ...
git reset HEAD~7 --hard      # roll back before mistake
git filter-repo ... --force  # tell filter-repo the alterations are expected
  • での拡張パターンに気付かなかったため、インストールに困惑しましたgitが、最終的にはgit-filter-repoのクローンを作成し、次のようにシンボリックリンクしました$(git --exec-path)
ln -s ~/github/newren/git-filter-repo/git-filter-repo $(git --exec-path)

1
新しい推奨するためUpvoted filter-repo(私は先月提示ツールstackoverflow.com/a/58251653/6309を
VonC

git-filter-repoこの時点では、間違いなく使用することをお勧めします。これはgit-filter-branch、よりもはるかに高速で安全であり、git履歴を書き換えるときに遭遇する多くの問題を回避します。この答えが対処すべきものであるため、うまくいけば、この答えはもう少し注目されますgit-filter-repo
Jeremy Caney、

4

私はまさにこの問題を抱えていましたが、git filter-branchに基づくすべての標準ソリューションは非常に低速でした。あなたが小さなリポジトリを持っているなら、これは問題ではないかもしれません、それは私のためでした。libgit2に基づく別のgitフィルタリングプログラムを作成しました。最初のステップとして、プライマリリポジトリのフィルタリングごとにブランチを作成し、次のステップとしてこれらをクリーンなリポジトリにプッシュします。私のリポジトリ(500Mb 100000コミット)では、標準のgitフィルターブランチメソッドに数日かかりました。私のプログラムは同じフィルタリングを行うのに数分かかります。

git_filterという素晴らしい名前があり、ここにあります:

https://github.com/slobobaby/git_filter

GitHubで。

誰かのお役に立てれば幸いです。


4

次のフィルターコマンドを使用して、タグとブランチを保持しながら、サブディレクトリを削除します。

git filter-branch --index-filter \
"git rm -r -f --cached --ignore-unmatch DIR" --prune-empty \
--tag-name-filter cat -- --all

ここの猫は何ですか?
rogerdpack 2016

4

価値のあるものとして、WindowsマシンでGitHubを使用する方法を次に示します。に存在するクローンリポジトリがあるとしC:\dir1ます。ディレクトリ構造は次のようになりますC:\dir1\dir2\dir3。このdir3ディレクトリは、新しい個別のリポジトリになりたいディレクトリです。

Github:

  1. 新しいリポジトリを作成します。 MyTeam/mynewrepo

バッシュプロンプト:

  1. $ cd c:/Dir1
  2. $ git filter-branch --prune-empty --subdirectory-filter dir2/dir3 HEAD
    返される:Ref 'refs/heads/master' was rewritten(fyi:dir2 / dir3は大文字と小文字を区別します。)

  3. $ git remote add some_name git@github.com:MyTeam/mynewrepo.git
    git remote add origin etc。機能しませんでした、「remote origin already exists」が返されました

  4. $ git push --progress some_name master


3

私がしたよう上記の、私は逆のソリューションを使用する必要がありました(私に触れていないすべてのコミットを削除dir/subdir/targetdir(必要に応じて)コミットの約95%の除去はかなりうまく動作するように見えました)。ただし、2つの小さな問題が残っています。

最初にfilter-branchコードを導入または変更するコミットを削除するという大きな仕事をしましたが、どうやらマージコミットはGitiverseのステーションの下にあります。

これはおそらく私が共存できる化粧品の問題です(彼は...目をそらしてゆっくりと後退します)

SECOND残る少数のコミットはかなりされているALL重複して!プロジェクトの履歴のほぼ全体に及ぶ、2番目の冗長なタイムラインを取得したようです。おもしろいこと(下の写真からわかるように)は、私の3つのローカルブランチがすべて同じタイムライン上にないということです(つまり、確かに存在し、ガベージコレクションだけではないのです)。

私が想像できる唯一のことは、削除されたコミットの1つが、おそらくfilter-branch 実際にdeleteを実行した単一のマージコミットであり、現在マージされていない各ストランドがコミットの独自のコピーを取得するときに並列タイムラインを作成したことです。(肩をすくめて私のTARDiSはどこですか?)私は本当にこの問題を解決できると確信していますが、それが起こったのか理解するのが大好きです。

クレイジーなmergefest-O-RAMAの場合、それは自分のコミット履歴に非常にしっかりと定着しているので、私はそれをそのままにしておくでしょう-私が近づくたびに私を脅します-それは実際には引き起こしていないようです非化粧品の問題と、それがTower.appでかなりきれいなため。


3

より簡単な方法

  1. インストールしgit splitsます。私は、jkeatingのソリューションに基づいて、それをgit拡張として作成しました。
  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 XY1 XY2

  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


「簡単な方法」と比較したこの方法の利点は、リモートがすでに新しいリポジトリ用に設定されているため、サブツリーの追加をすぐに実行できることです。実際、この方法は私にとって(たとえなくてもgit splits)より簡単に見える
MM

このソリューションを投稿してくれたAndrewDへの小道具。私はそれはOSX(上で動作させるために彼のレポをフォークしているgithub.com/ricardoespsanto/git-splitsそれは誰にも便利です場合)
ricardoespsanto

2

実際にファイルをクリーンアップするには、ガベージコレクションの前に「git reflog expire --expire = now --all」のようなものが必要になる場合があります。git filter-branchは単に履歴の参照を削除しますが、データを保持するreflogエントリは削除しません。もちろん、最初にこれをテストしてください。

これを行うと、ディスクの使用量が劇的に減少しましたが、初期状態は多少異なりました。おそらく--subdirectory-filterはこの必要性を無効にしますが、私はそれを疑っています。


2

https://github.com/vangorra/git_splitで git_splitプロジェクトをチェックしてください

gitディレクトリを独自の場所にある独自のリポジトリに変換します。サブツリー面白いビジネスはありません。このスクリプトは、gitリポジトリ内の既存のディレクトリを取得し、そのディレクトリを独自の独立したリポジトリに変換します。途中で、指定したディレクトリの変更履歴全体がコピーされます。

./git_split.sh <src_repo> <src_branch> <relative_dir_path> <dest_repo>
        src_repo  - The source repo to pull from.
        src_branch - The branch of the source repo to pull from. (usually master)
        relative_dir_path   - Relative path of the directory in the source repo to split.
        dest_repo - The repo to push to.

1

これをあなたのgitconfigに入れてください:

reduce-to-subfolder = !sh -c 'git filter-branch --tag-name-filter cat --prune-empty --subdirectory-filter cookbooks/unicorn HEAD && git reset --hard && git for-each-ref refs/original/ | cut -f 2 | xargs -n 1 git update-ref -d && git reflog expire --expire=now --all && git gc --aggressive --prune=now && git remote rm origin'

1

gitサブツリーはすべてすばらしいと思いますが、移動したいgitマネージコードのサブディレクトリはすべてeclipseでした。したがって、egitを使用している場合、それは非常に簡単です。移動するプロジェクトを選択してチーム->接続を解除し、チーム->共有して新しい場所に移動します。デフォルトでは古いリポジトリの場所を使用しようとしますが、既存の選択をオフにして、新しい場所を選択して移動できます。すべてのあられエジット。


3
サブツリーの「すばらしい」部分は、サブディレクトリの履歴が乗ってくるということです。あなたが歴史を必要としないなら、あなたの痛々しいほど簡単な方法は行く方法です。
pglezen

0

あなたは簡単にhttps://help.github.com/enterprise/2.15/user/articles/splitting-a-subfolder-out-into-a-new-repository/を試すことができます

これでうまくいきました。上記の手順で私が直面した問題は

  1. このコマンドではgit filter-branch --prune-empty --subdirectory-filter FOLDER-NAME BRANCH-NAME ザ・でBRANCH-NAMEあるマスター

  2. 保護の問題が原因でコミット時に最後のステップが失敗した場合-https ://docs.gitlab.com/ee/user/project/protected_branches.html


0

私は非常に簡単な解決策を見つけました。そのアイデアは、リポジトリをコピーして、不要な部分を削除することです。これがどのように機能するかです:

1)分割したいリポジトリのクローンを作成します

git clone git@git.thehost.io:testrepo/test.git

2)gitフォルダーに移動します

cd test/

2)不要なフォルダを削除してコミットします

rm -r ABC/
git add .
enter code here
git commit -m 'Remove ABC'

3)BFGで不要なフォルダの履歴を削除します

cd ..
java -jar bfg.jar --delete-folders "{ABC}" test
cd test/
git reflog expire --expire=now --all && git gc --prune=now --aggressive

複数のフォルダの場合は、カンマを使用できます

java -jar bfg.jar --delete-folders "{ABC1,ABC2}" metric.git

4)削除したファイル/フォルダが履歴に含まれていないことを確認します

git log --diff-filter=D --summary | grep delete

5)これでABCのないクリーンなリポジトリができたので、新しいオリジンにプッシュするだけです

remote add origin git@github.com:username/new_repo
git push -u origin master

それでおしまい。手順を繰り返して、別のリポジトリを取得できます。

ステップ3でXY1、XY2を削除してXYZ-> ABCの名前を変更するだけです


ほぼ完璧な...しかし、現在空になっている古いコミットをすべて削除する "git filter-branch --prune-empty"を忘れていました。オリジンマスターにプッシュする前にやる!
ZettaCircl

間違いがあり、古い空のコミットを削除した後も「再プッシュ」したい場合は、「git push -u origin master --force-with-lease」を実行します
ZettaCircl
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.