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

113 git git-submodules

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

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

ape*_*arr 117

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

安装

git clone https://github.com/apenwarr/git-subtree.git

cd git-subtree
sudo rsync -a ./git-subtree.sh /usr/local/bin/git-subtree
Run Code Online (Sandbox Code Playgroud)

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

make doc
make install
Run Code Online (Sandbox Code Playgroud)

用法

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

# 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
Run Code Online (Sandbox Code Playgroud)

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

  • git-subtree现在是git的一部分(如果你安装contrib),从1.7.11开始 (17认同)
  • git子树岩! (9认同)
  • 那么`git rm -rf./ foo`会从`HEAD`中删除`foo`,但不会过滤`my-project`的完整历史记录.然后,`git submodule add git@github.com:my-user/new-project.git foo`只使`foo`成为从`HEAD`开始的子模块.在这方面,脚本`filter-branch`是优越的,因为它允许实现"就好像如果subdir从一开始就是一个子模块" (7认同)
  • 但是,避免使用子模块不是git子树的重点吗?我的意思是,你确实是git-subtree的作者(除非有昵称冲突),但它看起来像git-subtree改变了,即使你显示的命令似乎仍然有效.我说得对吗? (3认同)

Pat*_*otz 38

结帐git filter-branch.

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

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

   git filter-branch --subdirectory-filter foodir -- --all
Run Code Online (Sandbox Code Playgroud)

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

  • 对我来说,从git repo中提取提交的全部意义在于我想保留历史记录. (2认同)

dbr*_*dbr 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
Run Code Online (Sandbox Code Playgroud)

这用于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
Run Code Online (Sandbox Code Playgroud)

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

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

你需要使用-fforce参数filter-branch再次运行refs/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'
Run Code Online (Sandbox Code Playgroud)

  • `git filter-branch`有(现在?)内置选项来删除空提交,即`--prune-empty`.关于`git filter-branch`的更好指南就是这个问题的答案:http://stackoverflow.com/questions/359424/detach-subdirectory-into-separate-git-repository (4认同)

Die*_*Epp 3

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

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

如果您不介意编写一个小而复杂的脚本,那么自动化此操作应该比较简单。是的,很简单,但也很痛苦。过去人们已经在 Git 中进行了历史重写,你可以搜索一下。

或者:克隆存储库,然后删除克隆中的论文,删除原始存储库中的应用程序。这将需要一分钟,它保证有效,并且您可以回到比尝试净化 git 历史记录更重要的事情上。并且不用担心历史记录的冗余副本占用的硬盘空间。