Git 子树:Git 子模块的替代品

互联网上到处都是关于为什么您不应该使用 Git 子模块的文章。虽然子模块在一些用例中很有用,但有几个缺点。

有其他选择吗?答案是肯定的!(至少)有两种工具可以帮助您跟踪项目中软件依赖关系的历史记录,同时允许您继续使用 git:

  • git subtree

  • 谷歌代码存储库

在这篇文章中,我将研究 git subtree,并说明为什么它相较于 Git 子模块是一种改进(尽管并不完美)。

什么是 git subtree?我为什么要使用它?

git subtree 允许您将一个存储库作为子目录嵌套在另一个存储库中。这是 Git 项目管理项目依赖关系的几种方式之一。

Git 之前/之后的子树示意图

为什么您可能要考虑 git subtree

  • 管理简单的工作流程很容易。

  • 支持旧版本的 git(甚至在 v1.5.2 之前)。

  • 子项目的代码在超级项目的克隆完成后立即可用。

  • git subtree 不要求您的存储库的用户学习任何新东西。他们可以忽略您使用 git subtree 来管理依赖关系的事实。

  • git subtree 不会像 Git 子模块那样添加新的元数据文件(即 .gitmodule)。

  • 可以修改模块的内容,而无需在其他地方有单独的依赖关系存储库副本。

缺点(但我们认为它们在很大程度上是可以接受的):

  • 您必须了解一种新的合并策略(即 git subtree)。

  • 为子项目向 upstream 贡献代码稍微复杂一些。

  • 您要负责不要在提交中混合超级项目和子项目代码。

如何使用 git subtree

git subtree 自 2012 年 5 月起在 Git 的库存版本中上市——v1.7.11 及更高版本。自制软件在 OSX 上安装的版本已经正确连接了子树,但是在某些平台上,您可能需要按照安装说明进行操作。

下面我为您展示一个使用 git subtree 跟踪 vim 插件的典型示例。

无需远程跟踪的快速、较脏的方式

如果您只想剪切粘贴几行代码,不妨看看这段话。首先在指定的 prefix 文件夹中添加 git subtree

git subtree add --prefix .vim/bundle/tpope-vim-surround https://bitbucket.org/vim-plugins-mirror/vim-surround.git main --squash

(常用实践是不要将子项目的全部历史记录存储在主存储库中,但如果您想保留它,只需省略 --squash 标记即可。)

上面的命令生成以下输出:

git fetch https://bitbucket.org/vim-plugins-mirror/vim-surround.git main
warning: no common commits
remote: Counting objects: 338, done.
remote: Compressing objects: 100% (145/145), done.
remote: Total 338 (delta 101), reused 323 (delta 89)
Receiving objects: 100% (338/338), 71.46 KiB, done.
Resolving deltas: 100% (101/101), done.
From https://bitbucket.org/vim-plugins-mirror/vim-surround.git
* branch main -} FETCH_HEAD
Added dir '.vim/bundle/tpope-vim-surround'

如您所见,这通过将 vim-surround 存储库的整个历史记录压缩成一个来记录 merge commit:

1bda0bd [3 minutes ago] (HEAD, stree) Merge commit 'ca1f4da9f0b93346bba9a430c889a95f75dc0a83' as '.vim/bundle/tpope-vim-surround' [Nicola Paolucci]
ca1f4da [3 minutes ago] Squashed '.vim/bundle/tpope-vim-surround/' content from commit 02199ea [Nicola Paolucci]

如果稍后您想从 upstream 存储库更新插件的代码,您可以直接进行 git subtree 拉取:

git subtree pull --prefix .vim/bundle/tpope-vim-surround https://bitbucket.org/vim-plugins-mirror/vim-surround.git main --squash

这非常快速、轻松,但是命令较为冗长且难以记住。我们可以通过将子项目添加为远程项目来缩短命令的时间。

将子项目添加为远程项目

将子树添加为远程存储库允许我们以更简短的形式对其进行引用:

git remote add -f tpope-vim-surround https://bitbucket.org/vim-plugins-mirror/vim-surround.git

现在我们可以添加子树(和以前一样),但是现在我们可以用简短的形式引用远程存储库了:

git subtree add --prefix .vim/bundle/tpope-vim-surround tpope-vim-surround main --squash

稍后更新子项目的命令变为:

git fetch tpope-vim-surround main
git subtree pull --prefix .vim/bundle/tpope-vim-surround tpope-vim-surround main --squash

反馈给上游

我们现在可以在本地工作目录中自由提交对子项目的修复。需要反馈给上游项目时,我们需要拷贝该项目并将其添加为另一个远程项目:

git remote add durdn-vim-surround ssh://git@bitbucket.org/durdn/vim-surround.git

现在我们可以使用 subtree push 命令,如下所示:

git subtree push --prefix=.vim/bundle/tpope-vim-surround/ durdn-vim-surround main
git push using: durdn-vim-surround main
Counting objects: 5, done.
Delta compression using up to 4 threads.
Compressing objects: 100% (3/3), done.
Writing objects: 100% (3/3), 308 bytes, done.
Total 3 (delta 2), reused 0 (delta 0)
To ssh://git@bitbucket.org/durdn/vim-surround.git
02199ea..dcacd4b dcacd4b21fe51c9b5824370b3b224c440b3470cb -} main

然后我们就准备好了,可以向软件包的维护人员提出拉去请求。

我可以在不使用 git subtree 命令的情况下做到这一点吗?

是的!是的,您可以。git subtree 与子树合并策略不同。即使出于某种原因 git subtree 不可用,您仍然可以使用合并策略。以下是操作方法。

将依赖关系添加为简单的 git remote

git remote add -f tpope-vim-surround https://bitbucket.org/vim-plugins-mirror/vim-surround.git

将依赖关系的内容读入存储库前,请务必记录一次合并,这样我们才能跟踪到目前为止插件的整个树历史记录:

git merge -s ours --no-commit tpope-vim-surround/main

哪些输出:

Automatic merge went well; stopped before committing as requested

然后,我们将插件存储库中最新树对象的内容读取到我们准备提交的工作目录中:

git read-tree --prefix=.vim/bundle/tpope-vim-surround/ -u tpope-vim-surround/main

现在我们可以提交了(这将是一个合并提交,它将保留我们读取的树的历史记录):

git ci -m"[subtree] adding tpope-vim-surround"
[stree 779b094] [subtree] adding tpope-vim-surround

当我们想要更新项目时,现在可以使用 git subtree 合并策略拉取:

git pull -s subtree tpope-vim-surround main

git subtree 是一个不错的替代方案

使用 Git 子模块一段时间后,您会看到 git subtree 解决了 Git 子模块的许多问题。和往常一样,对于 Git 的所有东西,都有一条学习曲线可以充分利用它的功能。

为您推荐

Bitbucket 博客

DevOps 学习路径

了解有关 Git 的更多信息

在此中心查找更多 Git 指南和资源。