【问题标题】:When to use git subtree?什么时候使用 git 子树?
【发布时间】:2015-12-01 04:15:41
【问题描述】:

git subtree 解决了什么问题? 我应该何时以及为什么要使用该功能?

我读到它是used for repository separation。但是为什么我不只是创建两个独立的存储库,而不是将两个不相关的存储库合二为一呢?

这个 GitHub 教程解释了how to perform Git subtree merges

我知道如何使用它,但不知道何时(用例)和为什么,以及它与@987654323的关系@。当我依赖另一个项目或库时,我会使用子模块。

【问题讨论】:

  • "repository separation" != "unrelated repositories" 认为你的 repo 中的依赖项并且你不想使用子模块(出于某种原因,也许你不喜欢它们不透明并且子模块中提交的路径与主 git repo 中的路径不匹配)。
  • @cyphar:你是说submodulesubtree 都或多或少地实现了相同的目标,即合并相关项目,唯一的区别是submodule 可能有点不太透明和更新子模块是一个两步操作,subtree 的缺点是提交消息会在两个项目之间全部混淆?
  • 嗯,在某些情况下,这并不是一个真正的缺点。例如,如果您需要将具有subtrees 的存储库一分为二,并且在依赖项中引入了错误,您将在引入错误的subtree 中找到确切的提交。使用子模块,您只会发现修改submodule 的提交会导致错误,如果您想快速找到submodule 中的哪个提交导致主项目中的错误,那么您就是SOL。
  • 这里有一篇文章比较了git subtree和git submodule的实例nering.dev/2016/git-submodules-vs-subtrees

标签: git git-subtree


【解决方案1】:

首先:我相信您的问题往往会得到强烈的固执己见的答案,在这里可能会被认为是题外话。但是我不喜欢这种 SO 政策,并且会将话题的边界向外推,所以我喜欢回答并希望其他人也这样做。

在您指向的 GitHub 教程中,有一个指向 How to use the subtree merge strategy 的链接,它提供了关于优点/缺点的观点:

比较子树与子模块的合并

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

但是,如果您使用 子模块,那么您可以选择不传输子模块对象。这可能是子树合并的问题。

此外,如果您对其他项目进行更改,如果您只使用子模块,则提交更改会更容易

以下是我基于上述观点的观点:

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

对于子模块来说它仍然太复杂了(sigh)但更容易理解:它只是一个 repo 中的一个 repo(他们熟悉层次结构),你可以做你的推拉像往常一样。

为子模块工作流程提供简单的包装脚本更容易恕我直言。

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

访问控制可能不同。还没有这个问题,但是如果不同的仓库需要不同的访问控制,有效地禁止某些用户进入某些子仓库,我想知道使用子模块方法是否更容易完成。

就我个人而言,我不确定自己要使用什么。所以我分享你的困惑:o]

【讨论】:

  • 这个答案是我见过的最自以为是的答案,尽管存在矛盾,因为它是唯一的答案,也是自我实现的预言。恼怒的叹息,对别人学习能力的末日论者态度,这是一个很嚣张的回答。您对政策的看法可能属于 Meta,它可能会有所帮助。答案本身,除了自私的绒毛之外,还不错。
  • @vgoff:你的批评是正确的。很抱歉看起来很自大——这只是 15 年以上的工作经验,这些人在这段时间里接受了不同版本控制系统中不同人的培训,并且仍然将文本文件复制到大量 .backup.<timestamp>。我想我一开始就明确表示这将是固执己见的。希望其他人能够提供更真实的见解,我很惊讶还没有人提供。
  • 我还是不明白。您是说submodule 是已弃用的旧库合并方式,而subtree 是新的闪亮方式?
  • 没有。文档至少没有提到两者中的任何一个已被弃用。对我来说,文档拥有最终决定权(错误除外)。完成类似的事情只是两个不同的工作流程。两者都有优点和缺点。对我来说,没有任何一个 git 大师回答的事实是对专家的确认,这些差异可以忽略不计。很可能使用子树合并策略,因为它是较早实施的策略,人们熟悉read-tree(以及分支/合并/远程)。 submodules 添加于
【解决方案2】:

当您在 git 的上下文中使用术语 'subtree' 时,应注意明确指出您在说什么,因为这里实际上有两个独立但相关的主题:

git-subtreegit subtree merge strategy

TL;DR

这两个子树相关的概念都可以有效地让您同时管理多个存储库。与git-submodule 相比,只有元数据以.gitmodules 的形式存储在根存储库中,并且您必须单独管理外部存储库。

更多详情

git subtree merge strategy基本上是使用您引用的命令的更手动的方法。

git-subtree 是一个包装外壳脚本,以促进更自然的语法。这实际上仍然是contrib 的一部分,并没有通过通常的手册页完全集成到 git 中。 documentation 与脚本一起存储。

这里是使用信息:

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>]

我遇到了很多关于子树主题的资源,因为我正计划写一篇自己的博客文章。如果我这样做了,我会更新这篇文章,但现在这里有一些与手头问题相关的信息:

您可以在this Atlassian blogNicola Paolucci 上找到您正在寻找的大部分内容:

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

有几个原因 你可能会发现subtree 更好用:

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

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

  • 您必须了解新的合并策略(即subtree)。
  • 为子项目贡献代码回 upstream 稍微复杂一些。
  • 在提交中不混合超级项目和子项目代码的责任在于您。

我也同意其中的大部分内容。我建议您查看这篇文章,因为它介绍了一些常见用法。

您可能已经注意到,他还写了一个跟进here,其中他提到了这种方法遗漏的一个重要细节......

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

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

我还发现this blog post 也非常有用。作者添加了他称为git-stree 的第三个子树方法。这篇文章值得一读,因为他在比较这三种方法方面做得很好。他对自己喜欢什么和不喜欢什么给出了个人意见,并解释了他为什么要创建第三种方法。

附加功能

结束思想

本主题既展示了git 的强大功能,也展示了当某个功能未达到目标时可能发生的分割。

我个人对git-submodule 产生了厌恶,因为我发现贡献者更难以理解。我也更喜欢在我的项目中管理我的所有依赖项,以促进易于重现的环境,而无需尝试管理多个存储库。然而,git-submodule 目前更为人所知,因此了解它显然是件好事,这取决于可能会影响您决定的受众。

【讨论】:

    【解决方案3】:

    我们有一个真正的用例,其中 git subtree 是一种拯救:

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

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

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

    这样,我们避免管理许多 repos 和许多分支。 git-subtree 将我们的生产力提高了数倍!

    更新

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

    我们创建了一个全新的存储库。然后我们将每个具有客户端分支的项目作为子树添加到该新存储库中。我们有一份 jenkins 的工作,就是定期将原始存储库的主更改推送到客户端分支。 我们只是使用带有功能和维护分支的典型 git 流来处理“客户端 repo”。

    我们的“客户”存储库还构建了我们也为这个特定客户改编的脚本。

    但是,提出的解决方案存在一个缺陷。

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

    【讨论】:

    • Marek,我面临着听起来一样的情况,而且我对 git 还比较陌生,并且在各种可能性中挣扎。我想详细了解您的设置。
    • 我创建了一个全新的存储库。然后我将每个具有客户端分支的项目作为子树添加到该存储库。我们有一份 jenkins 的工作,即将对原始存储库的更改推送到客户端分支。在我们的客户仓库中,我们在具有功能、维护分支的 master 上正常工作。
    • 陷阱是我们离产品的主要核心开发越来越远。因此,该特定客户端的可能升级变得越来越困难。在我们的例子中,在子树已经远离主路径之前的项目状态是可以的,所以子树至少引入了引入默认 git 流的顺序和可能性。
    • 我们的“客户”存储库还构建了我们也在为这个特定客户进行调整的脚本。
    • 我想建议您将来自 cmets 的其他信息合并到您的答案中;他们肯定让这成为一个更好的答案。
    【解决方案4】:

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

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

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

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

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

    • 与子模块相比,我发现子树方法更可靠、更实用 :)(我是初学者)

    干杯!

    【讨论】:

      【解决方案5】:

      为了补充上述答案,使用子树的另一个缺点是与子模块相比的 repo 大小。

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

      因此,如果代码库高度模块化,那么加起来会很快。

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

      【讨论】:

      • 存储不是问题。 熵是问题!例如,您有 1000 个 10KB 到 100KB 的工具,每个工具共享一个 35 GB 的公共代码库(因为它包含来自不同来源的大量模块)。使用子模块,您总共传输大约 36 GB,但使用 git subtree 可能超过 1 TB!另请注意,如果涉及git gc 和 ZFS dedup(对象包),子模块具有明显的不公平优势。因此,AFAICS 较小的代码库(repo 大小而不是 repo 计数)应该与子模块一起使用,更大的与 monorepo 一起使用。我还没有发现子树有什么用处。
      • @tino Git 将使用通用代码删除子树就好了。我只是做了一些实验来确认。对于签出的代码,您需要运行 ZFS 之类的东西。但是子模块并没有什么不同。
      猜你喜欢
      • 2011-12-29
      • 1970-01-01
      • 1970-01-01
      • 2011-04-10
      • 1970-01-01
      • 2020-08-16
      • 1970-01-01
      • 2011-01-29
      • 1970-01-01
      相关资源
      最近更新 更多