什么时候使用git子树?


79

git subtree解决什么问题?什么时候以及为什么要使用该功能?

我读过它用于存储库分离。但是,为什么我不只是创建两个独立的存储库,而不是将两个不相关的存储库合并为一个?

这个GitHub教程解释了如何执行Git子树合并

我有点知道如何使用它,但是不知道何时(用例),原因以及与它的关系git submodule。当我依赖于另一个项目或库时,我将使用子模块。


1
“ repository分离”!=“不相关的存储库”认为您的存储库中的依赖项,并且您不想使用子模块(由于某些原因,也许您不喜欢它们不透明,并且提交中的路径在子模块与主git repo中的路径不匹配)。
cyphar

1
@cyphar:你是说这两个submodulesubtree都或多或少实现其纳入相关项目同样的目标,唯一的区别是,submodule可能会有点不太透明的和更新的子模块是一个两步操作和的缺点subtree是,提交消息会在两个项目之间混合在一起吗?
Lernkurve 2015年

1
好吧,在某些情况下这并不是真正的缺点。例如,如果您需要将包含的存储库subtree一分为二,并且在依赖项中引入了错误,则subtree可以在引入了该错误的中找到确切的提交。使用子模块,您将只发现submodule引起该错误的提交,如果您想快速查找submodule主项目中导致该错误的提交,则您就是SOL 。
cyphar

1
这是一篇文章,将git子树和git子模块与nering.dev/2016/git-submodules-vs-subtrees的
8ctopus

Answers:


57

在上下文中使用“子树”一词时,您应该小心地明确指出您在说什么,git因为这里实际上有两个独立但相关的主题:

git-subtreegit subtree合并策略

TL; DR

两种与子树相关的概念都可以有效地使您在一个仓库中管理多个仓库。与git-submodule相比,在根存储库中仅存储元数据,而git-submodule则采用.gitmodules的形式,因此您必须分别管理外部存储库。

更多细节

git子树合并策略基本上是使用您引用的命令的更手动的方法。

git-subtree是一个包装程序外壳程序脚本,用于促进更自然的语法。实际上,这仍然是contribgit的一部分,没有与常规手册页完全集成到git中。而是将文档与脚本一起存储。

这是用法信息:

NAME
----
git-subtree - Merge subtrees together and split repository into subtrees


SYNOPSIS
--------
[verse]
'git subtree' add   -P <prefix> <commit>
'git subtree' add   -P <prefix> <repository> <ref>
'git subtree' pull  -P <prefix> <repository> <ref>
'git subtree' push  -P <prefix> <repository> <ref>
'git subtree' merge -P <prefix> <commit>
'git subtree' split -P <prefix> [OPTIONS] [<commit>]

当我计划编写自己的博客文章时,我遇到了很多有关子树的资源。如果我愿意的话,我将更新这篇文章,但现在这里有一些有关手头问题的信息:

多少你正在寻找上可以找到什么样的这个Atlassian的博客尼古拉·保卢奇的相关部分如下:

为什么使用子树而不是子模块?

您可能会发现subtree更好使用的原因有几个:

  • 简单工作流程的管理很容易。
  • git支持的旧版本(甚至在之前v1.5.2)。
  • clone超级项目完成后,即可使用子项目的代码。
  • subtree不需要您存储库的用户学习任何新知识,他们可以忽略您subtree用来管理依赖项的事实。
  • subtree不会像submodules那样 添加新的元数据文件.gitmodule
  • 可以修改模块的内容,而无需在其他地方具有依赖项的单独存储库副本。

我认为缺点是可以接受的:

  • 您必须了解一种新的合并策略(例如subtree)。
  • upstream向子项目回发代码稍微复杂一些。
  • 在提交中不混合上层项目代码和下层项目代码的责任在于您。

我也很同意这一点。我建议您检查一下这篇文章,了解它的一些常用用法。

您可能已经注意到,他还在这里写了一篇后续文章,其中提到了此方法遗漏的重要细节...

git-subtree 目前无法包含遥控器!

目光短浅的原因可能是由于人们在处理子树时经常手动添加一个远程对象,但这也没有存储在git中。作者详细介绍了他编写的补丁程序,用于将该元数据添加到git-subtree已经生成的提交中。在此之前,您可以通过修改提交消息或将其存储在另一个提交中来做类似的事情。

我也发现这篇博客文章也很有帮助。作者将他调用的第三个子树方法添加git-stree到混合中。这篇文章值得一读,因为他在比较这三种方法方面做得很好。他给出了自己对做什么和不喜欢做什么的个人看法,并解释了为什么他创建了第三种方法。

附加功能

总结思想

本主题展示了git当功能仅丢失标记时的功能以及可能发生的细分。

我个人git-submodule对此很反感,因为我发现它使贡献者难以理解。我还更喜欢在项目中管理所有依赖项,以提供易于重现的环境,而无需尝试管理多个存储库。git-submodule但是,当前它的知名度要高得多,因此意识到这一点并取决于可能会影响您决定的听众显然是一件好事。


12

首先:我相信您的问题容易得到有力的答案,在这里可能被认为是题外话。但是,我不喜欢这样的SO政策,并且会稍微超出话题的边界,因此我想回答,并希望其他人也这样做。

在您所指向的GitHub教程上,有一个指向如何使用子树合并策略的链接,该链接给出了优点/缺点的观点:

比较子树与子模块的合并

使用子树合并的好处是,它需要来自存储库用户较少管理负担。它适用于较旧的客户端(在Git v1.5.2之前),克隆后您便拥有了代码。

但是,如果您使用子模块,则可以选择不传输子模块对象。这可能与子树合并有关。

另外,如果您对另一个项目进行了更改,则仅使用子模块就更容易提交更改

基于以上几点,这是我的观点:

我经常与不是常规git用户的人们(= committers)一起工作,有些人(并且将永远)与版本控制作斗争。对他们进行有关如何使用子模块合并策略的教育基本上是不可能的。它涉及附加遥控器的概念,即合并,分支,然后将其全部混合到一个工作流程中。从上游拉动和向上游推动是一个分为两个阶段的过程。由于分支机构很难为他们所理解,所以这一切都是无望的。

对于子模块,对于他们来说仍然太复杂(叹息),但更容易理解:它只是一个回购中的一个回购(他们熟悉层次结构),您可以照常进行推送和拉取。

提供简单的包装脚本对于子模块工作流来说更容易。

对于具有许多子存储库的大型超级存储库,选择不克隆某些子存储库的数据是该子模块的重要优势。我们可以根据工作要求和磁盘空间使用量来限制它。

访问控制可能有所不同。还没有这个问题,但是如果不同的存储库需要不同的访问控制,从而有效地禁止某些用户访问某些子存储库,我想知道使用子模块方法是否更容易做到这一点。

就我个人而言,我不确定自己该怎么用。因此,我与您分享困惑:o]


3
尽管有矛盾,但这个答案是我见过的最坚决的答案,因为它是唯一的答案,并且是自我实现的预言。愤怒的叹息,毁灭者的态度对别人的学习能力,这是一个非常傲慢的答案。您对政策的看法可能属于Meta,可能会有所帮助。答案本身,除了自我服务之外,还是相当不错的。
vgoff

1
@vgoff:您的批评是正确的。抱歉,您似乎太自大了-当时只有15年以上的工作经验,这些人在不同的版本控制系统中接受了不同时间的培训,并且仍然将文本文件复制到大量文件中.backup.<timestamp>。我想我一开始就明确指出了这一点。希望其他人能够提供更多的事实见解,令我惊讶的是,还没有人感到惊讶。
cfi

我还是不明白。您是说这submodule是合并使用的库的不赞成使用的旧方法,subtree而是新的有效方法吗?
Lernkurve 2015年

否。文档至少没有提到这两个都不推荐使用。对我来说,文档拥有最终决定权(错误除外)。要做类似的事情只是两个不同的工作流程。两者都有优点和缺点。对我而言,尚未有一位git大师回答,这一事实证实了专家的区别是可以忽略的。最有可能使用子树合并策略,因为它是较早实现的方法,并且人们很熟悉read-tree(并且无论如何分支/合并/远程)。submodules添加于
cfi

5

我们有一个真实的用例,其中git子树是救恩:

我们公司的主要产品是高模块化的,并在单独的存储库中的多个项目中开发。所有模块都有各自的路线图。整个产品由具体版本的所有模块组成。

同时,我们为每个客户定制了整个产品的具体版本-每个模块都有单独的分支。有时必须一次在多个项目中进行自定义(cross-module customization)。

为了为定制产品提供单独的产品生命周期(维护,功能分支),我们引入了git子树。我们为所有自定义模块提供了一个git-subtree存储库。我们的定制是每天将所有原始存储库“ git子树推送”回到定制分支。

这样,我们避免管理许多回购和分支。git-subtree多次提高了我们的生产力!

更新

有关发布到评论的解决方案的更多详细信息:

我们创建了一个全新的存储库。然后,我们将每个具有客户端分支的项目添加到该新仓库中,作为子树。我们有一个詹金斯(Jenkins)工作,正在将对原始存储库的主更改定期推回客户分支。我们仅使用带有功能和维护分支的典型git flow与“客户回购”合作。

我们的“客户”存储库还构建了一些脚本,我们也对该脚本进行了调整。

但是,存在提出的解决方案的陷阱。

随着我们离产品的主要核心开发越来越远,对该特定客户进行可能的升级变得越来越困难。在我们的例子中,在子树已经不是主要路径之前,项目状态还可以,所以子树至少引入了顺序和引入默认git flow的可能性。


Marek,我面对着同样的情况,我对git和陷入困境的可能性还比较陌生。我想进一步了解您的设置。
goug

我创建了一个全新的存储库。然后,我将具有客户端分支的每个项目添加到该存储库中作为子树。我们有一个詹金斯工作,正在将对原始存储库的更改推回客户分支。在我们的客户回购中,我们通常在具有功能,维护分支的master上工作。
Marek Jagielski,

陷阱在于我们离产品的主要核心开发越来越远。因此,对该特定客户端进行可能的升级变得越来越困难。在我们的例子中,在子树已经不是主要路径之前,项目状态还可以,所以子树至少引入了顺序和引入默认git flow的可能性。
Marek Jagielski

我们的“客户”存储库还需要构建脚本,我们也正在针对该特定客户进行调整。
Marek Jagielski

1
我建议您将评论中的其他信息纳入答案;他们肯定是一个更好的答案。
James Skemp

5

基本上,Git子树是Git子模块方法的替代方案:有很多缺点,或者我想说,使用git子模块时需要非常小心。例如,当您有“一个”存储库,而在“一个”内部时,您已使用子模块添加了另一个名为“两个”的存储库。您需要注意的事项:

  • 当您在“ two”中更改某些内容时,需要在“ two”中提交并推送,如果您位于顶级目录(即“ one”中),则您的更改将不会突出显示。

  • 当未知用户尝试克隆您的“一个”存储库时,克隆“一个”后,该用户需要更新子模块以获取“两个”存储库

这些是一些要点,为了更好地理解,我建议您观看以下视频:https : //www.youtube.com/watch?v=UQvXst5I41I

  • 为了克服这些问题,发明了子树方法。要了解有关git-subtree的基础知识,请查看以下内容:https : //www.youtube.com/watch?v=t3Qhon7burE

  • 与子模块相比,我发现子树方法更可靠,更实用:)(我是很多初学者,可以说这些话)

干杯!


2

为了增加上述答案,与子模块相比,使用子树的另一个缺点是存储库大小。

我没有任何现实指标,但是考虑到每次在模块上进行一次推送,使用该模块的任何地方都会在父模块上获得相同更改的副本(随后在这些存储库上进行更新)。

因此,如果代码库被高度模块化,那么这将很快增加。

但是,鉴于存储价格始终在下降,这可能不是一个重要因素。


存储不是问题。 熵才是问题!例如,您有1000个10KB至100KB的工具,每个工具共享一个说35 GB的通用代码库(因为它包含来自不同来源的大量模块)。使用子模块,您可以全部传输约36 GB,但使用git子树则可能超过1 TB!还要注意,子模块在涉及git gcZFS dedup(对象包)方面具有明显的不公平优势。因此,AFAICS较小的代码库(明智的是存储库大小而不是明智的存储库计数)应与子模块一起使用,较大的代码库应与monorepo一起使用。我还没有发现子树有任何用途。
蒂诺

@tino Git将使用通用代码对子树进行精简。我只是做了一些实验来确认。对于签出的代码,您需要运行类似ZFS的代码。但是子模块没有什么不同。
马赛厄斯
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.