如何提取一个git子目录并从中创build一个子模块?

几个月前我开始了一个项目,并将所有内容存储在主目录中。 在我的主目录“Project”中有几个包含不同内容的子目录:Project / paper包含一个用LaTeX Project / sourcecode编写的文档/ RailsApp包含了我的Rails应用程序。

“项目”被GIT化,“纸”和“RailsApp”目录中都有很多提交。 现在,我想为我的“RailsApp”使用cruisecontrol.rb,不知道是否有办法在不丢失历史的情况下从“RailsApp”中创build一个子模块。

有什么build议么?

现在有一个更简单的方法来做到这一点比手动使用git filter-branch: git subtree

安装

git clone https://github.com/apenwarr/git-subtree.git cd git-subtree sudo rsync -a ./git-subtree.sh /usr/local/bin/git-subtree 

或者,如果你想要的手册页和所有

 make doc make install 

用法

将较大的块分成较小的块:

 # Go into the project root cd ~/my-project # Create a branch which only contains commits for the children of 'foo' git subtree split --prefix=foo --branch=foo-only # Remove 'foo' from the project git rm -rf ./foo # Create a git repo for 'foo' (assuming we already created it on github) mkdir foo pushd foo git init git remote add origin git@github.com:my-user/new-project.git git pull ../ foo-only git push origin -u master popd # Add 'foo' as a git submodule to `my-project` git submodule add git@github.com:my-user/new-project.git foo 

有关详细的文档(手册页),请阅读git-subtree.txt

签出gitfilter分支 。

手册页的Examples部分展示了如何将子目录提取到自己的项目中,同时保留其所有历史logging,并放弃其他文件/目录的历史logging(正是您要查找的内容)。

重写仓库看起来好像foodir/是它的项目根源,并放弃所有其他的历史:

  git filter-branch --subdirectory-filter foodir -- --all 

因此,您可以将库子目录变成自己的存储库。
请注意--filter-branch选项与修订选项分开,并且 – 将所有分支和标记重写。

这样做的一个方法是反过来 – 除了你想要保留的文件之外的所有东西。

基本上, 做一个存储库的副本 ,然后使用git filter-branch删除所有文件/文件夹,但是你想保留。

例如,我有一个项目,我想从中提取文件tvnamer.py到一个新的仓库:

 git filter-branch --tree-filter 'for f in *; do if [ $f != "tvnamer.py" ]; then rm -rf $f; fi; done' HEAD 

这使用git filter-branch --tree-filter来通过每个提交,运行命令并重新提交生成的目录内容。 这是非常破坏性的(所以你应该只在你的仓库的副本上做这个),并可能需要一段时间(约300分提交和约20个文件的仓库约1分钟)

上面的命令只是在每个修订版本上运行下面的shell脚本,当然你必须修改它(使其不包含你的子目录而不是tvnamer.py ):

 for f in *; do if [ $f != "tvnamer.py" ]; then rm -rf $f; fi; done 

最明显的问题是它留下所有提交的消息,即使它们与其余文件无关。 脚本git-remove-empty-commits修正了这个..

 git filter-branch --commit-filter 'if [ z$1 = z`git rev-parse $3^{tree}` ]; then skip_commit "$@"; else git commit-tree "$@"; fi' 

你需要使用-f force参数再次运行filter-branch任何东西在refs/original/ (基本上是一个备份)

当然,这永远不会是完美的,例如,如果你的提交信息提到其他文件,但它是关于一个git电stream允许(就我所知,无论如何)。

再一次,只有在你的仓库的副本上运行这个! – 但总之,要删除除“thisismyfilename.txt”之外的所有文件:

 git filter-branch --tree-filter 'for f in *; do if [ $f != "thisismyfilename.txt" ]; then rm -rf $f; fi; done' HEAD git filter-branch -f --commit-filter 'if [ z$1 = z`git rev-parse $3^{tree}` ]; then skip_commit "$@"; else git commit-tree "$@"; fi' 

如果你想把一些文件的子集转移到一个新的版本库中,但是要保留这个版本的历史,那么基本上你会得到一个全新的历史。 这将工作的方式基本如下:

  1. 创build新的存储库。
  2. 对于旧版本库的每个修订版本,将更改模块合并到新版本库中。 这将创build您现有项目历史的“副本”。

如果你不介意写一个小而毛茸茸的脚本,这应该是直接的自动化。 直截了当的,是的,但也是痛苦的。 过去人们已经在Git中做过历史重写,你可以做一个search。

或者:克隆存储库,并删除克隆中的纸张,删除原来的应用程序。 这将需要一分钟,这是保证工作,你可以回到比试图净化你的git历史更重要的事情。 不要担心冗余备份历史占用的硬盘空间。