我们用C#开发.NET企业软件。我们正在寻求改进我们的版本控制系统。我以前用过汞,一直在我们公司试用。然而,由于我们开发企业产品,我们非常关注可重复使用的组件或模块。我一直在尝试使用mercurial的子repo来管理组件和依赖关系,但遇到了一些困难。以下是源代码控制/依赖性管理的基本要求:
- 可重复使用的组件
- 由源共享(用于调试)
- 依赖第三方二进制文件和其他可重用组件
- 可以在消费产品的背景下开发并致力于源代码管理
- 依赖项
- 产品依赖于第三方二进制文件和其他可重用组件
- 依赖项有自己的依赖项
- 依赖关系中的版本冲突应通知开发人员
这是我一直在使用的汞的结构:
可重复使用的组件:
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
备注
- 回购在CAPS中
- 所有子转发都被假定为子转发
- 第三方(二进制)库和内部(源)组件都是位于库文件夹中的子目录
- 第三方库保存在单独的mercurial repo中,以便消费项目可以引用特定版本的库(即,旧项目可能引用NLog v1.0,新项目可能引用NLog v2.0)
- 所有Visual Studio.csproj文件都位于第4级(proj*文件夹),允许对依赖项进行相对引用(即../../libs/NLog/NLog.dll,适用于所有引用NLog的Visual Studio项目)
- 所有Visual Studio.sln文件都位于第二级(src文件夹),因此在将组件"共享"到使用组件或产品中时不会包含这些文件
- 开发人员可以自由地组织他们认为合适的源文件,只要这些源是使用Visual Studio项目的proj*文件夹的子级(即,proj*文件可以有n个子级,包含各种源/资源)
- 如果Bob正在开发SHARED2组件和PROD1产品,那么他在PROD1_SLN存储库中对SHARED2源(比如属于proj3的源)进行更改并提交这些更改是完全合法的。我们不介意有人在消费项目的背景下开发库
- 内部开发的组件(SHARED1和SHARED2)通常由源代码包含在使用项目中(在Visual Studio中,添加对项目的引用,而不是浏览到dll引用)。这允许增强调试(进入库代码),允许Visual Studio管理何时需要重建项目(何时修改依赖项),并允许在需要时修改库(如上面的说明所述)
问题
如果Bob正在处理PROD1,而Alice正在处理SHARED1,Bob如何知道Alice何时提交对SHARED1的更改。目前使用Mercurial时,Bob被迫在每个子博客中手动拉取和更新。如果他从PROD_SLN repo推送/拉取到服务器,他永远不会知道subpos的更新。这在Mercurial wiki上有描述。当Bob从服务器中提取PROD_SLN的最新版本时,如何通知他subpos的更新?理想情况下,应该通知他(最好是在拉取期间),然后必须手动决定他想要更新哪个子站点。
假设SHARED1引用NLog v1.0(在mercurial中为commit/rev abc),SHARED2引用NLog v2.0(在merculial中为提交/rev xyz)。如果Bob在PROD1中吸收了这两个成分,那么他应该意识到这一差异。虽然从技术上讲,VisualStudio/.NET允许两个程序集引用不同版本的依赖项,但我的结构不允许这样做,因为所有依赖NLog的.NET项目的NLog路径都是固定的。Bob怎么知道他的两个依赖项存在版本冲突?
如果Bob正在为PROD1设置存储库结构,并希望包含SHARED2,他如何知道SHARED2需要什么依赖项?根据我的结构,他必须手动克隆(或在服务器上浏览)SHARED2_SLN repo,然后在libs文件夹中查找,或者在.hgsub文件中查找峰值,以确定他需要包括哪些依赖项。理想情况下,这将是自动化的。如果我在产品中包含SHARED2,那么SHARED1和NLog也会自动神奇地包含在内,如果存在与其他依赖项的版本冲突,则会通知我(请参阅上面的问题2)。
更大的问题
汞是正确的溶液吗?
有更好的水银结构吗?
这是对subpos的有效使用吗(即Mercurial开发人员将subpos标记为最后的功能)?
使用mercurial进行依赖关系管理有意义吗?我们可以使用另一种工具进行依赖性管理(也许是内部NuGet提要?)。虽然这对第三方依赖关系很有效,但它确实会给内部开发的组件带来麻烦(即,如果它们是主动开发的,开发人员将不得不不断更新提要,我们将不得不在内部为它们提供服务,并且它不允许由消耗性项目修改组件(注8和问题2)。
你有更好的企业.NET软件项目解决方案吗?
参考文献
我读过几个SO问题,发现这一个很有帮助,但公认的答案建议使用专门的依赖性工具。虽然我喜欢这样一个工具的功能,但它不允许从一个消耗性项目中修改和提交依赖关系(请参阅更大的问题4)。
这可能不是你想要的答案,但我们最近有Mercurial新手用户使用子转发的经验,我一直在寻找机会来传递我们的经验。。。
总之,根据经验,我的建议是:无论Mercurial次级回购多么有吸引力,都不要使用它们。相反,找到一种并排排列目录的方法,并调整构建以应对这种情况。
无论将次级回购的修订与母回购的修订联系在一起看起来多么有吸引力,但在实践中并不奏效。
在转换的所有准备过程中,我们收到了来自多个不同来源的建议,即次级回购很脆弱,实施得不好,但我们还是继续进行了,因为我们希望回购和次级回购之间进行原子提交。这些建议——或者我对它的理解——更多地是关于原则,而不是实际后果。
只有当我们使用Mercurial和次级回购时,我才真正理解了这个建议。这里(根据记忆)是我们遇到的各种问题的例子。
- 您的用户最终会在更新和合并过程中遇到麻烦
- 有些人会更新母回购,而不是子回购
- 有些人会从子回购推送,ang。hgsubstate不会得到更新
- 您最终将"丢失"子回购中所做的修订,因为合并后会有人设法使.hgsubstate处于不正确的状态
- 有些用户会遇到.hgsubstate已经更新,但子repo还没有更新的情况,然后你会收到非常神秘的错误消息,并且会花很多小时试图弄清楚发生了什么
- 如果你为发行版做标记和分支,那么如何为父回购和子回购做好这件事的说明将长达数十行。(我甚至有一位和蔼、温和的Mercurial专家帮我写说明书!)
所有这些事情在专业用户手中已经够烦人的了,但当你向新手用户推出Mercurial时,它们真的是一场噩梦,也是浪费时间的根源。
因此,在投入大量时间进行次级回购转换后,几周后,我们将次级回购转换为回购。因为我们在转换中有大量的历史,通过.hgsubstate提到了子回购,这给我们留下了更复杂的东西。
我只希望我真的很感激早些时候所有建议的实际效果,例如在Mercurial的"最后的度假胜地"页面上:
但我需要管理子项目
再说一遍,不要那么确定。像Mozilla这样有大量依赖关系的重要项目在不使用subpos的情况下做得很好。如果不使用subpos,大多数较小的项目几乎肯定会过得更好。
编辑:对空壳回购的思考
对于免责声明,我没有任何经验
不,我认为他们中的很多人都是。您仍在使用子转发,因此所有相同的用户问题都适用(当然,除非您可以为每个步骤提供包装脚本,以消除人类提供正确选项来处理子转发的需要。)
还要注意的是,你引用的维基页面确实列出了一些关于外壳转发的具体问题:
- 过于严格地跟踪项目/和somelib之间的关系/
- 无法检查或推送项目/如果somelib/source repo
- 不可用-缺乏对递归diff、log和
- commit惊奇的状态递归性质
编辑2-进行试用,让您的所有用户参与
我们真正开始意识到我们有问题的时候,有一次多个用户开始进行提交、拉取和推送,包括对子回购的更改。对我们来说,对这些问题作出回应为时已晚。如果我们早点认识他们,我们本可以更容易、更简单地做出反应。
因此,在这一点上,我认为我能提供的最好建议是建议您在石头上雕刻布局之前对项目布局进行试运行。
我们把全面试验留到太晚才做出改变,即使在那时,人们也只对母回购进行了改变,而不是子回购——所以我们直到太晚才看到全貌。
换句话说,无论你考虑什么布局,都要在该布局中创建一个存储库结构,并让很多人进行编辑。试着在各种转发/子转发中放入足够多的真实代码,这样人们就可以进行真正的编辑,即使他们会被抛弃。
可能的结果:
- 你可能会发现一切都很好——在这种情况下,你会花一些时间来获得确定性
- 另一方面,你可能会比花时间试图弄清楚结果更快地发现问题
- 你的用户也会学到很多东西
问题1:
当在父"shell"repo中执行此命令时,将从不存在的默认拉取位置遍历所有子位置和列表变更集:
hg incoming --subrepos
如果选中了"--subrepos"选项(在同一窗格上),则可以通过点击TortoiseHg中"同步"窗格上的"传入"按钮来完成相同的操作。
感谢mercurial IRC频道的用户在这里提供的帮助。
问题2&3:
首先,我需要修改我的repo结构,使父repo真正成为hgwiki上推荐的"shell"repo。我将把这一点发挥到极致,并说外壳不应包含任何内容,只应包含作为子级的子站点。总之,将src重命名为main,将docs移动到main下的subrep中,并将prod文件夹更改为subrep。
共享d1_SLN:
SHARED1_SLN-+-libs----NLOG
|
+-misc----KEY
|
+-main----SHARED1-+-docs
| +-proj1
| +-proj2
|
+-tools---NANT
共享d2_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
- 所有共享库和产品都有自己的回购(SHARED1、SHARED2和PROD)
- 如果您需要独立处理共享库或产品,有一个shell可用(我的repo以_SLN结尾),它使用hg来管理依赖项的修订。shell只是为了方便起见,因为它不包含任何内容,只包含subpos
- 当发布共享库或产品时,开发人员应该列出用于创建发布的所有依赖项及其hg-revs/变更集(或者最好是人性化标记)。此列表应保存在库或产品(SHARED1、SHARED2或PROD)的repo中的文件中,而不是shell。请参阅下面的注释A,了解如何解决问题2&3
- 如果我发布了一个共享库或产品,我应该在项目中的repo中放置匹配的标签,并且为了方便起见,它是shell,然而,如果shell出现了问题(这是@Clare的回答中根据实际经验表达的担忧),这真的应该无关紧要,因为shell本身是愚蠢的,并且不包含任何内容
- Visual Studio sln文件进入共享库或产品的repo(SHARED1、SHARED2或PROD)的根目录,同样,而不是shell。结果是,如果我在PROD中包含SHARED1,我可能会得到一些从未打开过的额外解决方案,但这并不重要。此外,如果我真的想在SHARED1上工作并运行它的单元测试(同时在PROD_SLN shell中工作),这真的很容易,只需打开所说的解决方案
注A:
关于上面的第3点,如果依赖关系文件使用类似于.hgsub的格式,但添加了rev/changeset/标记,那么可以自动获取依赖关系。例如,我希望在我的新产品中使用SHARED1。将SHARED1克隆到我的libs文件夹中,并更新到提示或最新版本标签。现在,我需要查看依赖项文件,a)将依赖项克隆到正确的位置,b)更新到指定的rev/changeset/标记。实现自动化非常可行。更进一步,它甚至可以跟踪rev/changeset/标记,并提醒开发人员共享库之间存在依赖冲突。
如果Alice正在积极开发SHARED1,而Bob正在开发PROD,则漏洞仍然存在。如果Alice更新SHARED1_SLN以使用NLog v3.0,Bob可能永远不会知道这一点。如果Alice更新了她的依赖文件以反映更改,那么Bob确实有信息,他只需要知道更改。
更大的问题1&4:
我认为这是一个源代码管理问题,而不是依赖项管理工具可以解决的问题,因为它们通常使用二进制文件,并且只有获得依赖项(不允许将更改提交回依赖项)。我的依赖性问题并非Mercurial独有。根据我的经验,所有的源代码管理工具都有相同的问题。SVN中的一个解决方案是只使用SVN:externals(或SVN副本),并递归地让每个组件都包含其依赖项,从而创建一个可能巨大的树来构建产品。然而,在VisualStudio中,这就不一样了,我只想包含一个共享项目的实例,并在任何地方引用它。正如@Clare的回答和Greg对我给hg邮件列表的电子邮件的回复所暗示的那样,尽可能保持组件的扁平化。
更大的问题2&3:
正如我在上面所阐述的,还有一个更好的结构。我相信我们有一个使用subpos的强大用例,我看不到可行的替代方案。正如@Clare的回答中所提到的,有一个阵营认为依赖关系可以在没有subpos的情况下进行管理。然而,我还没有看到任何证据或实际的参考资料来支持这一说法。
更大的问题5:
仍然对更好的想法持开放态度。。。