1889

多数のサブディレクトリを含むGitリポジトリがあります。現在、サブディレクトリの 1 つが他のサブディレクトリと無関係であり、別のリポジトリにデタッチする必要があることがわかりました。

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

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

明確にするために、次の構造を持っています。

XYZ/
    .git/
    XY1/
    ABC/
    XY2/

しかし、代わりにこれが欲しい:

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

26 に答える 26

1503

簡単な方法™</h1>

これは非常に一般的で便利な方法であるため、Git の大君主はこれを非常に簡単にしましたが、Git の新しいバージョン (>= 1.7.11 2012 年 5 月) を使用する必要があります。最新の 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

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

     mkdir ~/<new-repo> && cd ~/<new-repo>
     git init
     git pull </path/to/big-repo> <name-of-new-branch>
    
  2. 新しいレポを GitHub などにリンクする

     git remote add origin <git@github.com:user/new-repo.git>
     git push -u origin master
    
  3. 必要に応じ<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.gitignoreおよびを使用してリポジトリを作成した場合は、LICENSE最初にプルする必要があります。

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その後、履歴にすべてが含まれている状態に戻ります。

そのため、「オリジン」から履歴を削除する場合 (つまり、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

とはいえ、必要があることがわかっている場合を除き、これらの手順を実行しないことをお勧めします。リポジトリをプッシュしても、バックアップ ファイルのクローンは作成されません。ローカル コピーにあるだけです。

クレジット

于 2013-07-25T17:10:06.293 に答える
1264

更新: このプロセスは非常に一般的であるため、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 が発見したように、これにより、プロジェクトのブランチとタグが新しいリポジトリに含まれたままになります。

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

于 2008-12-11T15:40:54.280 に答える
139

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

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

もちろん、最初に「clone --no-hardlinks」リポジトリでテストしてから、Paul がリストした reset、gc、および prune コマンドを実行します。

于 2009-06-05T13:15:20.737 に答える
98

新しいリポジトリから古い履歴を適切に削除するには、手順の後でもう少し作業を行う必要があることがわかりました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が触れないパックファイルに履歴が残っている可能性があります。それを細かく裂き、新しいパックファイルを作成し、未使用のオブジェクトを削除します。

    git repack -ad
    

これについては、フィルターブランチのマニュアルに説明があります。

于 2009-10-19T21:10:21.860 に答える
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

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


次の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
于 2011-06-09T15:41:37.473 に答える
19

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

The 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/subprojectmvまた、コマンドを使用しないでくださいmove

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

  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

于 2015-08-06T15:26:51.363 に答える
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/

于 2010-03-22T20:55:26.383 に答える
13

元の質問では、XYZ/ABC/(*files) を ABC/ABC/(*files) にする必要があります。自分のコードに受け入れられた回答を実装した後、実際に XYZ/ABC/(*files) が ABC/(*files) に変更されることに気付きました。filter-branch の man ページには、次のようにも書かれています。

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

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

フィルター分岐後に連続性が失われました

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

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

于 2012-04-17T05:12:06.860 に答える
7

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

すなわち

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

gc prune の後に、次のことも行います。

$ 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 リセット --hard
$ git push ...ABC.git HEAD
于 2009-07-25T10:01:26.310 に答える
4

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

Github:

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

Bashプロンプト:

  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

于 2012-02-07T19:07:35.393 に答える
4

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

git filter-branch --index-filter \
"git rm -r -f --cached --ignore-unmatch DIR" --prune-empty \
--tag-name-filter cat -- --all
于 2010-10-28T02:36:37.497 に答える
3

上で述べたように、逆の解決策 (私の に触れていないすべてのコミットを削除するdir/subdir/targetdir) を使用する必要がありました。ただし、2 つの小さな問題が残っています。

FIRSTは、filter-branchコードを導入または変更するコミットを削除するという大成功を収めましたが、明らかに、マージ コミットは Gitiverse のそのステーションの下にあります。

これは私がおそらく我慢できる外見上の問題です (彼はこう言います...目をそらしてゆっくりと後ずさりします)

2番目に、残っているいくつかのコミットはほとんどすべて複製されています! 私は、プロジェクトのほぼすべての歴史にまたがる、2 つ目の冗長なタイムラインを取得したようです。興味深い点 (下の図からわかるように) は、私の 3 つのローカル ブランチがすべて同じタイムライン上にあるわけではないことです (これが、それが存在し、ガベージ コレクションだけではない理由です)。

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

クレイジーなマージフェスト-O-RAMA の場合は、私のコミット履歴にしっかりと定着しているので、そのままにしておく可能性があります。非外観上の問題はなく、Tower.app では非常にきれいです。

于 2013-05-31T10:01:26.073 に答える
2

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

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

于 2009-06-12T02:49:26.910 に答える
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.
于 2016-01-06T02:42:05.683 に答える
1

git サブツリーはすべて問題なく素晴らしいと確信していますが、移動したい git マネージ コードのサブディレクトリはすべて eclipse でした。したがって、egit を使用している場合は、非常に簡単です。移動したいプロジェクトをチーム -> 切断し、チーム -> 新しい場所で共有します。デフォルトでは古いリポジトリの場所を使用しようとしますが、既存の使用の選択をオフにして、新しい場所を選択して移動することができます。すべての雹egit。

于 2016-02-10T16:57:12.867 に答える
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'
于 2013-03-29T20:18:09.520 に答える
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に従ってください。

于 2019-01-10T07:23:15.530 に答える