How to extract a git subdirectory and make a submodule out of it?

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

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

33728 次浏览

如果您想要将一些文件子集转移到一个新的存储库,但是保留了历史记录,那么基本上最终将得到一个全新的历史记录。工作方式基本上如下:

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

如果您不介意编写一个小而复杂的脚本,那么将其自动化应该比较简单。直截了当,是的,但也很痛苦。过去有人用 Git 重写过历史,你可以搜索一下。

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

检查 Git 过滤树枝

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

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

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

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

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

基本上,是存储库的 复印一份,然后使用 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

That uses git filter-branch --tree-filter to go through each commit, run the command and recommit the resulting directories content. This is extremely destructive (so you should only do this on a copy of your repository!), and can take a while (about 1 minute on a repository with 300 commits and about 20 files)

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

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

The biggest obvious problem is it leaves all commit messages, even if they are unrelated to the remaining file. The script git-remove-empty-commits, fixes this..

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/中的任何内容(基本上是一个备份)

Of course this will never be perfect, for example if your commit messages mention other files, but it's about as close a git current allows (as far as I'm aware anyway).

同样,只能在存储库的一个副本上运行此命令!-但总的来说,要删除除“ 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 更简单的方法: Git 子树

Installation

注意: 从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

Or if you want the man pages and all

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

酷 AJ86apenwarr的答案非常相似。我在两者之间来回穿梭,试图理解其中任何一个缺失的部分。下面是它们的组合。

首先将 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

Below is a copy of above with the customize-able names replaced and using https instead. Root folder is now ~/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