11

我们用 C# 开发 .NET 企业软件。我们正在寻求改进我们的版本控制系统。我以前使用过 mercurial,并且一直在我们公司尝试使用它。但是,由于我们开发企业产品,因此我们非常关注可重用的组件或模块。我一直在尝试使用 mercurial 的子存储库来管理组件和依赖项,但遇到了一些困难。以下是源代码控制/依赖管理的基本要求:

  1. 可重复使用的组件
    1. 由源共享(用于调试)
    2. 依赖于 3rd 方二进制文件和其他可重用组件
    3. 可以在消费产品的背景下开发并致力于源代码控制
  2. 依赖项
    1. 产品依赖于 3rd 方二进制文件和其他可重用组件
    2. 依赖有自己的依赖
    3. 应通知开发人员依赖项中的版本冲突

这是我一直在使用的 mercurial 结构:

一个可重用的组件:

SHARED1_SLN-+-docs
            |
            +-libs----NLOG
            |
            +-misc----KEY
            |
            +-src-----SHARED1-+-proj1
            |                 +-proj2
            |
            +-tools---NANT

第二个可重用组件,使用第一个:

SHARED2_SLN-+-docs
            |
            +-libs--+-SHARED1-+-proj1
            |       |         +-proj2
            |       |
            |       +-NLOG
            |
            +-misc----KEY
            |
            +-src-----SHARED2-+-proj3
            |                 +-proj4
            |
            +-tools---NANT            

消耗这两种成分的产品:

PROD_SLN----+-docs
            |
            +-libs--+-SHARED1-+-proj1
            |       |         +-proj2
            |       |
            |       +-SHARED2-+-proj3
            |       |         +-proj4
            |       |
            |       +-NLOG
            |
            +-misc----KEY
            |
            +-src-----prod----+-proj5
            |                 +-proj6
            |
            +-tools---NANT

笔记

  1. 回购是大写
  2. 所有子回购都被假定为子回购
  3. 第 3 方(二进制)库和内部(源)组件都是位于 libs 文件夹中的子存储库
  4. 第 3 方库保存在单独的 mercurial 存储库中,以便使用项目可以引用特定版本的库(即旧项目可能引用 NLog v1.0,新项目可能引用 NLog v2.0)。
  5. 所有 Visual Studio .csproj 文件都位于第 4 级(proj* 文件夹),允许相对引用依赖项(即所有引用 NLog 的 Visual Studio 项目的 ../../../libs/NLog/NLog.dll)
  6. 所有 Visual Studio .sln 文件都位于第二级(src 文件夹),因此在将组件“共享”到使用组件或产品时不会包含它们
  7. 开发人员可以自由组织他们认为合适的源文件,只要源是使用 Visual Studio 项目的 proj* 文件夹的子级(即 proj* 文件夹可以有 n 个子级,包含各种源/资源)
  8. 如果 Bob 正在开发 SHARED2 组件和 PROD1 产品,那么他在 PROD1_SLN 存储库中更改 SHARED2 源(比如属于 proj3 的源)并提交这些更改是完全合法的。我们不介意有人在消费项目的上下文中开发图书馆。
  9. 内部开发的组件(SHARED1 和 SHARED2)通常由源包含在使用项目中(在 Visual Studio 中添加对项目的引用而不是浏览到 dll 引用)。这允许增强调试(单步执行库代码),允许 Visual Studio 管理何时需要重建项目(修改依赖项时),并允许在需要时修改库(如上述注释中所述)。

问题

  1. 如果 Bob 在 PROD1 上工作,而 Alice 在 SHARED1 上工作,那么 Bob 怎么知道 Alice 何时将更改提交到 SHARED1。目前使用 Mercurial,Bob 被迫在每个子存储库中手动拉取和更新。如果他从 PROD_SLN 存储库推/拉到服务器,他永远不知道子存储库的更新。Mercurial wiki对此进行了描述。当 Bob 从服务器中提取最新的 PROD_SLN 时,如何通知他子存储库的更新?理想情况下,应该通知他(最好在拉取期间),然后必须手动决定他要更新哪些子存储库。

  2. 假设 SHARED1 引用 NLog v1.0(mercurial 中的 commit/rev abc),SHARED2 引用 Nlog v2.0(mercurial 中的 commit/rev xyz)。如果 Bob 在 PROD1 中吸收了这两个组件,则应该让他意识到这种差异。虽然从技术上讲,Visual Studio/.NET 将允许 2 个程序集引用不同版本的依赖项,但我的结构不允许这样做,因为 NLog 的路径对于依赖于 NLog 的所有 .NET 项目都是固定的。Bob 怎么知道他的两个依赖有版本冲突?

  3. 如果 Bob 正在为 PROD1 设置存储库结构并希望包含 SHARED2,那么他如何知道 SHARED2 需要哪些依赖项?使用我的结构,他必须手动克隆(或在服务器上浏览)SHARED2_SLN 存储库,然后查看 libs 文件夹,或者查看 .hgsub 文件以确定他需要包含哪些依赖项。理想情况下,这将是自动化的。如果我在我的产品中包含 SHARED2,则 SHARED1 和 NLog 也会自动包含在内,如果与其他依赖项存在版本冲突,则会通知我(参见上面的问题 2)。

更大的问题

  1. mercurial 是正确的解决方案吗?

  2. 有更好的水银结构吗?

  3. 这是对 subrepos 的有效使用(即 Mercurial 开发人员将subrepos标记为最后手段的功能)?

  4. 使用 mercurial 进行依赖管理有意义吗?我们可以使用另一个工具进行依赖管理(可能是内部 NuGet 提要?)。虽然这对 3rd 方依赖项很有效,但它确实会给内部开发的组件带来麻烦(即,如果它们是积极开发的,开发人员将不得不不断更新提要,我们必须在内部为它们提供服务,而且它不允许由消费项目修改的组件(注 8 和问题 2)。

  5. 您有更好的企业 .NET 软件项目解决方案吗?

参考

I have read several SO questions and found this one to be helpful, but the accepted answer suggests using a dedicated tool for dependencies. While I like the features of such a tool it does not allowed for dependencies to be modified and committed from a consuming project (see Bigger Question 4).

4

2 回答 2

12

这可能不是您要寻找的答案,但我们最近有新手 Mercurial 用户使用子存储库的经验,我一直在寻找机会传递我们的经验......

总之,我基于经验的建议是:无论 Mercurial 子存​​储库多么吸引人,都不要使用它们。相反,找到一种方法来并排布置您的目录,并调整您的构建以应对这种情况。

然而,将子回购中的修订与父回购中的修订联系在一起似乎很有吸引力,但它在实践中是行不通的。

在转换的所有准备过程中,我们收到了来自多个不同来源的建议,即子存储库很脆弱并且没有很好地实现 - 但我们还是继续进行,因为我们希望在存储库和子存储库之间进行原子提交。这个建议——或者我对它的理解——更多地谈论了原则而不是实际后果。

只有当我们使用 Mercurial 和一个子回购时,我才真正正确地理解了这些建议。这里(根据记忆)是我们遇到的各种问题的示例。

  • 您的用户最终将与更新和合并过程作斗争。
  • 有些人会更新父仓库而不是子仓库
  • 有些人会从子仓库推送,ang .hgsubstate 不会得到更新。
  • 您最终将“丢失”在子回购中所做的修订,因为有人会在合并后设法使 .hgsubstate 处于不正确的状态。
  • 一些用户会遇到 .hgsubstate 已更新但子 repo 没有更新的情况,然后您会收到非常神秘的错误消息,并且会花费很多时间来弄清楚发生了什么。
  • 而且,如果您为发布进行标记和分支,则有关如何为父仓库和子仓库正确执行此操作的说明将长达数十行。(我什至有一个很好的驯服的 Mercurial 专家帮我写说明!)

所有这些事情在专家用户手中已经够烦人了——但是当你向新手用户推出 Mercurial 时,它们就是一场真正的噩梦,也是浪费大量时间的根源。

因此,在投入大量时间通过子回购进行转换后,几周后,我们将子回购转换为回购。因为我们在通过 .hgsubstate 引用子存储库的转换中有大量历史记录,所以它给我们留下了更复杂的东西。

我只希望我能在更早的时候真正欣赏所有建议的实际后果,例如在 Mercurial 的“最后手段的特征”页面中:

但我需要管理子项目!

再说一次,不要那么肯定。像 Mozilla 这样具有大量依赖项的重要项目在不使用子存储库的情况下也可以正常工作。如果不使用 subrepos,大多数较小的项目几乎肯定会更好。


编辑:关于shell 回购的想法

有了免责声明,我对他们没有任何经验......

不,我不认为他们中的很多人是。您仍在使用子存储库,因此所有相同的用户问题都适用(除非您可以为每个步骤提供包装脚本,当然,以消除人类提供正确选项来处理子存储库的需要。)

另请注意,您引用的 wiki 页面确实列出了 shell 存储库的一些特定问题:

  • 过于严格地跟踪 project/ 和 somelib/ 之间的关系
  • 无法检查或推送项目/如果 somelib/ 源 repo 变为
  • 不可用 缺乏对递归 diff、log 和
  • 提交的状态递归性质令人惊讶

编辑 2 - 进行试验,涉及所有用户

我们真正开始意识到我们遇到问题的时候是多个用户开始提交、拉取和推送——包括对子存储库的更改。对我们来说,现在回应这些问题已经太晚了。如果我们早点知道他们,我们本可以更轻松、更简单地做出回应。

所以在这一点上,我认为我能提供的最好建议是建议您在布局确定之前对项目布局进行试运行

我们离开全面试验直到太晚才做出改变,即便如此,人们也只在父 repo 中进行了更改,而不是在子 repo 中进行了更改——所以我们直到太晚才看到全貌。

换句话说,无论您考虑什么布局,在该布局中创建一个存储库结构,并让很多人进行编辑。尝试将足够的真实代码放入各种 repos/sub-repos 中,以便人们可以进行真正的编辑,即使它们是一次性的。

可能的结果:

  • 您可能会发现一切正常 - 在这种情况下,您将花费一些时间来获得确定性。
  • 另一方面,您可能会比花时间试图找出结果更快地发现问题
  • 您的用户也会学到很多东西。
于 2012-07-24T21:52:35.530 回答
3

问题一:

此命令在父“shell”存储库中执行时将遍历所有子存储库并从默认拉取位置列出不存在的变更集:

hg incoming --subrepos

如果您选中了“--subrepos”选项(在同一窗格上),则可以通过单击 TortoiseHg 中“同步”窗格上的“传入”按钮来完成相同的操作。

感谢 mercurial IRC 频道的用户在这里提供帮助。

问题 2 和 3:

首先,我需要修改我的 repo 结构,以便父 repos 是hg wiki 上推荐的真正的“shell” repos 。我将把这一点发挥到极致,说shell 不应该包含任何内容,只包含 subrepos 作为 children。总之,将 src 重命名为 main,将 docs 移动到 main 下的 subrepo 中,并将 prod 文件夹更改为 subrepo。

SHARED1_SLN:

SHARED1_SLN-+-libs----NLOG
            |
            +-misc----KEY
            |
            +-main----SHARED1-+-docs
            |                 +-proj1
            |                 +-proj2
            |
            +-tools---NANT

SHARED2_SLN:

SHARED2_SLN-+-libs--+-SHARED1-+-docs
            |       |         +-proj1
            |       |         +-proj2
            |       |
            |       +-NLOG
            |
            +-misc----KEY
            |
            +-main----SHARED2-+-docs
            |                 +-proj3
            |                 +-proj4
            |
            +-tools---NANT            

PROD_SLN:

PROD_SLN----+-libs--+-SHARED1-+-docs
            |       |         +-proj2
            |       |         +-proj2
            |       |
            |       +-SHARED2-+-docs
            |       |         +-proj3
            |       |         +-proj4
            |       |
            |       +-NLOG
            |
            +-misc----KEY
            |
            +-main----PROD----+-docs
            |                 +-proj5
            |                 +-proj6
            |
            +-tools---NANT
  1. 所有共享库和产品都有自己的存储库(SHARED1、SHARED2 和 PROD)。
  2. 如果您需要独立处理共享库或产品,则有一个可用的 shell(我的 repos 以 _SLN 结尾),它使用 hg 来管理依赖项的修订。shell 只是为了方便,因为它不包含任何内容,只有 subrepos。
  3. 在发布共享库或产品的发布时,开发人员应列出用于创建发布的所有依赖项及其 hg revs/changesets(或最好是人类友好的标签)。此列表应保存在 lib 或产品(SHARED1、SHARED2 或 PROD)的 repo 中的文件中,而不是 shell中。请参阅下面的注释 A,了解这如何解决问题 2 和 3。
  4. 如果我发布共享库或产品的版本,我应该将匹配的标签放在项目 repo 中,并且它是 shell 以方便起见,但是,如果 shell 出现问题(@Clare 的回答中的真实经验表达的担忧),这真的不重要,因为外壳本身是愚蠢的并且不包含任何内容。
  5. Visual Studio sln 文件再次进入共享库或产品存储库(SHARED1、SHARED2 或 PROD)的根目录,而不是 shell。结果是如果我在 PROD 中包含 SHARED1,我最终可能会得到一些我从未打开过的额外解决方案,但这并不重要。此外,如果我真的想在 SHARED1 上工作并运行它的单元测试(在 PROD_SLN shell 中工作时),这真的很容易,只需打开上述解决方案即可。

注一:

关于上面的第 3 点,如果依赖文件使用类似于 .hgsub 的格式但添加了 rev/changeset/tag,则可以自动获取依赖项。例如,我想在我的新产品中使用 SHARED1。将 SHARED1 克隆到我的 libs 文件夹并更新到提示或最后一个版本标签。现在,我需要查看依赖项文件并 a) 将依赖项克隆到正确的位置 b) 更新到指定的 rev/changeset/tag。自动化这一点非常可行。更进一步,它甚至可以跟踪 rev/changeset/tag 并提醒开发人员共享库之间存在依赖冲突。

如果 Alice 正在积极开发 SHARED1 而 Bob 正在开发 PROD,则会留下一个漏洞。如果 Alice 更新 SHARED1_SLN 以使用 NLog v3.0,Bob 可能永远不会知道这一点。如果 Alice 更新她的依赖文件以反映更改,那么 Bob 确实有信息,他只需要知道更改。

更大的问题 1 和 4:

我相信这是一个源代码控制问题,而不是可以通过依赖项管理工具解决的问题,因为它们通常使用二进制文件并且只获取依赖项(不允许将更改提交回依赖项)。我的依赖问题并不是 Mercurial 独有的。根据我的经验,所有源代码控制工具都有同样的问题。SVN 中的一种解决方案是仅使用 svn:externals(或 svn 副本)并递归地让每个组件都包含其依赖项,从而创建一个可能巨大的树来构建产品。然而,这在 Visual Studio 中分崩离析,我真的只想包含一个共享项目的实例并在任何地方引用它。正如@Clare 的回答Greg 对我发给 hg 邮件列表的电子邮件的回复所暗示的那样, 尽量保持元件平整。

更大的问题 2 和 3:

正如我上面所阐述的,有一个更好的结构。我相信我们有一个强大的使用 subrepos 的用例,我没有看到可行的替代方案。正如@Clare 的回答中提到的,有一个阵营认为可以在没有 subrepos 的情况下管理依赖项。但是,我还没有看到任何证据或实际参考来支持这一说法。

更大的问题5:

仍然对更好的想法持开放态度......

于 2012-07-25T09:01:55.090 回答