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

问题描述 投票:111回答:5

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

“Project”是GITified,“paper”和“RailsApp”目录中都有很多提交。现在,因为我想使用cruisecontrol.rb作为我的“RailsApp”,我想知道是否有办法在不丢失历史的情况下从“RailsApp”中创建一个子模块。

git git-submodules
5个回答
116
投票

现在有一种比手动使用git filter-branch更简单的方法:git subtree

Installation

注意git-subtree现在是git的一部分(如果你安装contrib),从1.7.11开始,所以你可能已经安装了它。您可以通过执行git subtree来检查。


要从源代码安装git-subtree(对于旧版本的git):

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

Usage

将较大的块拆分为较小的块:

# 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 [email protected]: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 [email protected]:my-user/new-project.git foo

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


38
投票

结帐git filter-branch

该手册页的Examples section显示了如何将子目录提取到其自己的项目中,同时保留其所有历史记录并丢弃其他文件/目录的历史记录(正是您正在寻找的内容)。

要重写存储库,看起来好像foodir/已成为其项目根目录,并丢弃所有其他历史记录:

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

因此,您可以将库子目录转换为自己的存储库。 请注意--filter-branch选项与修订选项分开,而--all则重写所有分支和标记。


13
投票

这样做的一种方法是反向删除除了要保留的文件之外的所有内容。

基本上,制作存储库的副本,然后使用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来完成每次提交,运行命令并重新生成结果目录内容。这是非常具有破坏性的(因此您只应在存储库的副本上执行此操作!),并且可能需要一段时间(在存储库中大约需要1分钟,包含300个提交和大约20个文件)

上面的命令只是在每个修订版上运行以下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-branchrefs/original/中的任何内容(基本上是备份)

当然,这将永远不会是完美的,例如,如果你的提交消息提到其他文件,但它就像git当前允许的那样接近(据我所知)。

同样,只能在您的存储库副本上运行它! - 但总的来说,删除所有文件,但“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'

4
投票

CoolAJ86apenwarr的答案非常相似。我在两者之间来回走动,试图理解任何一个中缺少的部分。以下是它们的组合。

首先将Git Bash导航到要拆分的git仓库的根目录。在我的例子中,这是~/Documents/OriginalRepo (master)

# move the folder at prefix to a new branch
git subtree split --prefix=SubFolderName/FolderToBeNewRepo --branch=to-be-new-repo

# create a new repository out of the newly made branch
mkdir ~/Documents/NewRepo
pushd ~/Documents/NewRepo
git init
git pull ~/Documents/OriginalRepo to-be-new-repo

# upload the new repository to a place that should be referenced for submodules
git remote add origin [email protected]:myUsername/newRepo.git
git push -u origin master
popd

# replace the folder with a submodule
git rm -rf ./SubFolderName/FolderToBeNewRepo
git submodule add [email protected]:myUsername/newRepo.git SubFolderName/FolderToBeNewRepo
git branch --delete --force to-be-new-repo

下面是上面的副本,其中替换了自定义名称并使用https代替。根文件夹现在是~/Documents/_Shawn/UnityProjects/SoProject (master)

# move the folder at prefix to a new branch
git subtree split --prefix=Assets/SoArchitecture --branch=so-package

# create a new repository out of the newly made branch
mkdir ~/Documents/_Shawn/UnityProjects/SoArchitecture
pushd ~/Documents/_Shawn/UnityProjects/SoArchitecture
git init
git pull ~/Documents/_Shawn/UnityProjects/SoProject so-package

# upload the new repository to a place that should be referenced for submodules
git remote add origin https://github.com/Feddas/SoArchitecture.git
git push -u origin master
popd

# replace the folder with a submodule
git rm -rf ./Assets/SoArchitecture
git submodule add https://github.com/Feddas/SoArchitecture.git
git branch --delete --force so-package

3
投票

如果您想将某些文件子集传输到新的存储库但保留历史记录,那么您基本上最终会得到一个全新的历史记录。这种方式的工作方式基本如下:

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

如果你不介意写一个小但毛茸茸的脚本,那么自动执行它应该有点简单。直截了当,是的,但也很痛苦。人们过去曾在Git中进行历史重写,你可以搜索它。

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

© www.soinside.com 2019 - 2024. All rights reserved.