【问题标题】:How to extract a git subdirectory and make a submodule out of it?如何提取 git 子目录并从中制作子模块?
【发布时间】:2010-10-29 13:00:49
【问题描述】:

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

"Project" 是 GITified,并且在 "paper" 和 "RailsApp" 目录中都有很多提交。现在,由于我想为我的“RailsApp”使用 Cruisecontrol.rb,我想知道是否有一种方法可以在不丢失历史记录的情况下使用“RailsApp”制作子模块。

【问题讨论】:

标签: git git-submodules


【解决方案1】:

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

安装

注意 从 1.7.11 开始,git-subtree 现在是 git 的一部分(如果您安装了 contrib),因此您可能已经安装了它。你可以通过执行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

用法

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

# 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

【讨论】:

  • 但是 git-subtree 的重点不是避免使用子模块吗?我的意思是,您确实是 git-subtree 的作者(除非有昵称冲突),但看起来 git-subtree 已更改,即使您显示的命令似乎仍然有效。我做对了吗?
  • git-subtree 现在是 1.7.11 的 git 的一部分(如果你安装了 contrib)
  • 好吧,git rm -rf ./fooHEAD 中删除了foo,但不会过滤my-project 的完整历史记录。然后,git submodule add git@github.com:my-user/new-project.git foo 只会使foo 成为从HEAD 开始的子模块。在这方面,脚本filter-branch 是优越的,因为它允许实现“就好像 subdir 从一开始就是一个子模块一样”
  • 感谢这个 -- git subtree docs 有点莫名其妙,这(对我来说)是我想做的最明显有用的事情......
  • 看起来这是仅限 Mac :(
【解决方案2】:

结帐git filter-branch

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

重写存储库,使其看起来好像foodir/ 是它的项目根,并丢弃所有其他历史记录:

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

因此,例如,您可以将库子目录变成自己的存储库。
请注意将filter-branch 选项与修订选项分开的--,以及用于重写所有分支和标签的--all

【讨论】:

  • 这对我来说效果很好。我注意到的唯一缺点是结果是一个包含所有提交的主分支。
  • @aceofspades:为什么会有这样的缺点?
  • 对我来说,从 git repo 中提取提交的全部意义在于我想保留历史记录。
【解决方案3】:

执行此操作的一种方法是相反的 - 删除除您要保留的文件之外的所有内容。

基本上,复制一个存储库,然后使用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 强制参数运行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'

【讨论】:

  • git filter-branch 有(现在?)一个用于删除空提交的内置选项,即--prune-empty。对git filter-branch 的更好指导在这个问题的答案中:stackoverflow.com/questions/359424/…
【解决方案4】:

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

首先将 Git Bash 导航到要拆分的 git repo 的根目录。在我的例子中是~/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 git@github.com:myUsername/newRepo.git
git push -u origin master
popd

# replace the folder with a submodule
git rm -rf ./SubFolderName/FolderToBeNewRepo
git submodule add git@github.com: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

【讨论】:

    【解决方案5】:

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

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

    如果您不介意编写一个小而复杂的脚本,那么自动化它应该有点简单。直截了当,是的,但也很痛苦。过去有人在 Git 中进行过历史改写,你可以搜索一下。

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

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-04-20
      • 2015-04-23
      • 1970-01-01
      • 2015-07-31
      • 2020-03-14
      相关资源
      最近更新 更多