灵活分支与静态分支(Git vs Clearcase/Accurev)

31
我的问题与Git处理分支的方式有关:每当你从某个提交分支时,除非你通过合并强制它,否则此分支将永远不会接收来自父分支的更改。
但是在其他系统中,例如Clearcase或Accurev,您可以指定如何使用某种继承机制填充分支:我的意思是,使用Clearcase,您可以使用config_spec说“获取在/ main / issue001分支上修改的所有文件,然后继续使用/ main上的文件或特定基线”进行操作。
在Accurev中,您也拥有类似的机制,该机制允许流(称为流)从上层分支接收更改,而无需合并或在分支上创建新的提交。
在使用Git时,难道您不感到缺少这个功能吗?您能列举出这种继承必须的场景吗?
谢谢
更新请阅读下面VonC的答案来实际聚焦我的问题。一旦我们同意“线性存储”和DAG(有向无环图)SCM具有不同的功能,那么我的问题就是:“哪些现实情况(尤其是针对公司而非OSS)线性可以做到DAG不可能做到的事情?它们值得吗?”

根据要求,添加了“现实生活”场景的差异和解释。 - VonC
针对您的评论,我添加了一条评论,并提到了使用自定义配置规范可能会面临“可重现性”问题,而不是合并/变基操作。 - VonC
9个回答

31
为了理解Git为什么不提供你所谓的“继承机制”(不涉及提交),你必须首先理解这些SCMs(例如Git与ClearCase)的一个核心概念
  • ClearCase使用线性版本存储:每个元素(文件或目录)的每个版本都与同一元素的上一个版本直接呈现线性关系。

  • Git使用DAG-有向无环图 :每个文件的“版本”实际上是树形全局变化集的一部分,该树本身又是一次提交的一部分。该版本的前一个版本必须在以前的提交中找到,通过单个有向无环图路径访问。

在线性系统中,配置规范可以指定几个规则来实现您所看到的“继承”(对于给定文件,首先选择某个版本,如果不存在,则选择另一个版本,如果不存在,则选择第三个版本,依此类推)。

分支是给定选择规则下的线性历史中的分叉点(在该之前的所有其他选择规则仍然适用,因此产生了“继承”效果)。

在DAG中,一个提交代表了你将要获得的所有"继承";没有版本的"累积"选择。在此图中,只有一条路径可以选择你在这个确切点(提交)所看到的所有文件。
分支只是此图中的一条新路径。
在Git中应用其他版本,你必须执行以下操作之一:
  • 将其他提交合并到你的分支中(就像stsquad's answer中提到的git pull一样),或者
  • 对你的分支进行变基(如Greg mentions所述)
但由于Git是基于DAG的SCM,它总会产生一个新的提交。
在Git中你失去的是某种"组合"(当你使用不同的连续选择规则选择不同的版本时),但这在一个DVCS(如"Distributed")中是不可行的:当你用Git创建一个分支时,你需要以明确定义且易于复制到其他存储库的起始点和内容为基础。
在纯粹的中央VCS中,你可以使用任何你想要的规则来定义你的工作区(在ClearCase中,你的“视图”,快照或动态)。
unknown-google在评论中(以及上面的问题)补充道:
那么,一旦我们看到两个模型可以实现不同的事情(线性 vs DAG),我的问题是:在线性模型可以做到DAG不可能的事情的真实场景(特别是对于公司而言)是什么?它们是否值得?
当涉及到选择规则的“真实场景”时,在线性模型中您可以为相同的文件集设置多个选择规则。
考虑这个“配置规范”(即使用ClearCase进行选择规则的“配置规范”):
element /aPath/... aLabel3 -mkbranch myNewBranch
element /aPath/... aLabel2 -mkbranch myNewBranch

它选择所有标记为“aLabel2”的文件(并从那里开始分支),除了那些标记为“aLabel3”的文件-并从那里开始分支-(因为该规则优先于提到“aLabel2”的规则)。
值得吗?
不值得。
实际上,ClearCase的UCM Flavor(ClearCase产品中包含的“统一配置管理”方法论,代表了从基本ClearCase使用中推导出的所有“最佳实践”)由于简化原因不允许这样做。一组文件称为“组件”,如果您想要针对给定标签(称为“基线”)进行分支,那么应按以下配置规范进行转换:
element /aPath/... .../myNewBranch
element /aPath/... aLabel3 -mkbranch myNewBranch
element /aPath/... /main/0 -mkbranch myNewBranch

你需要选择一个起点(这里是“aLabel3”)并从那里开始。如果你也想要“aLabel2”文件,你需要将所有“aLabel2”文件合并到“myNewBranch”中。
这是在DAG中不需要做的“简化”,其中图的每个节点都代表一个唯一定义的分支“起点”,无论涉及哪些文件集。
合并和变基足以将该起点与给定文件集的其他版本组合起来,以实现所需的“组合”,同时保持该特定历史记录在分支中的孤立状态。
总体目标是在“一致的组件”中应用“一致的版本控制操作”。
一个“一致”的文件集是处于一个明确定义的一致状态:
- 如果有标签,则所有文件都有标签。 - 如果分支,则所有文件都将从同一个唯一的起点分支出。
这在DAG系统中很容易实现;在线性系统中可能更难(特别是在“Base ClearCase”中,“配置说明”可能会很棘手),但在UCM方法论中,这种基于线性工具的方法是强制执行的。
与使用私有选择规则技巧(例如ClearCase的某些选择规则顺序)实现“组合”不同,您只需使用VCS操作(rebase或merge)即可实现它,这些操作留下了明显的痕迹供所有人跟踪(而不是开发人员专用或仅在一些而非所有开发人员之间共享的配置规范)。再次强调它强制实施了连贯性,与“动态灵活性”相对应,后者可能难以在以后复制。
这使您可以离开VCS(版本控制系统)领域,进入主要关注“可重复性”的SCM(软件配置管理)领域。并且这些(SCM特性)可以通过基于线性或基于DAG的VCS实现。

VonC,这正是我在实际提问之前应该添加的解释类型。是的,我已经全面阅读了Scott Chacon的GIT内部,所以我认为我对GIT的工作方式有一定的了解。我还有“基本Clearcase”经验。 因此,一旦我们看到这两个模型可以实现不同的事情(线性vs DAG),我的问题是:在线性模型下可以做出DAG无法实现的真实场景(尤其是对于公司而言)吗?它们有效吗? - jbhope
感谢VonC的解释。 简单来说,不管“线性模式”给了多少灵活性,似乎DAG才是正确的选择。 我想我们可以在没有使用GIT(正如我们使用CC时所做的那样)动态构建树的能力以及不能尝试将您的分支与另一个分支的特定更改一起构建,而是使用合并,还原等方式来解决它。 还有人坚持使用好老式的CC吗? - jbhope
你在DAG和“带分支的线性结构”有何不同方面失去了我。我看到你的解释集中在数据结构的使用上有所不同,但我不明白它们在根本上有何不同的数据结构。 - Otto
实际上,这个答案是胡说八道。Clearcase将单独的元素(文件、目录)与版本的单向无环图存储在一起。分支上的版本加上合并超链接创建了该图。Git的问题在于,树形结构中任何产品的视图/切片/剪切都必须先提交和合并才能查看,这是在任何回溯类型的场景和维护旧版本的企业部署中的严重限制。你的答案似乎基于对ClearCase的理解不足。 - Jiri Klouda
@JiriKlouda 所以你的意思是不在于有 DAG vs. 没有 DAG 的区别(因为这两个工具都有 DAG),而是在于以文件为中心 vs. 以仓库为中心,前者允许组合多个选择规则,后者只允许选择一个修订版本?请注意,UCM 更接近后者。我还想在你的回答中读到关于全树提交的缺点,包括后向移植和维护方面的缺点。谢谢。 - VonC
显示剩余4条评论

3
听起来您需要的可能是 git rebase。重新定义一个分支,从它的原始分支点上脱离,并在其他一些分支点上重新连接。 (实际上,通过按顺序将每个分支的补丁应用到新的分支点上来实现rebase,从而创建了一组新的补丁。) 在您的示例中,您可以将分支rebase到较高分支的当前末端,从而“继承”对另一个分支所做的所有更改。

1
不确定git rebase是否是答案。它会创建一个新的提交,而且据我所知,在重新设置基础之前,它甚至会修改分支历史记录。 关键是Clearcase或Accurev不会为此创建新的提交。 使用Clearcase,您可以配置您的“视图”以从不同的分支“下载”内容,这是您无法使用GIT的功能,但我的问题是:这很重要吗?也许git rebase就足够了,尽管它不能做同样的事情。 - jbhope

3
我不太清楚您的要求,但似乎您需要使用Git的跟踪语义。当您从一个起点分支时,您可以执行以下操作:

git -t -b my_branch origin/master

然后未来的 "git pull" 将自动将 origin/master 合并到您的工作分支中。然后,您可以使用 "git cherry -v origin/master" 查看差异。在发布更改之前,您可以使用 "git rebase" 清理历史记录,但是一旦您的历史记录是公开的(即其他人正在关注该分支),就不应该使用rebase。

确切地说,正如Greg所指出的那样,git rebase可以做到这一点,但是你总是会创建一个新的提交,不是吗? 使用Clearcase,您不需要这样做,但我想知道您是否会失去什么。我没有看到“真正的继承”在哪种情况下有很大的区别,我正在尝试找到一个例子。 - jbhope
其实并没有真正丢失,因为引用日志会告诉你,但是是的,Git 交互式变基重写了历史记录,因此您将丢失有关来自哪里的信息。但是,并不一定非要使用变基,如果您只合并,则所有历史记录都将得到保留。这取决于您是否认为保留所有那些错别字修复和中间步骤的历史记录很重要。 - stsquad

2

您是否注意到使用GIT也可以检出特定文件版本?

只需使用以下命令:

git checkout [< tree-ish >] [--] < paths >

根据配置规范,任何现有版本的文件(路径)都可以加载到工作树中。引用git-checkout文档:

以下步骤将检出主分支,将Makefile还原到两个版本之前,不小心删除了hello.c,并从索引中获取它:

$ git checkout master             
$ git checkout master~2 Makefile             
$ rm -f hello.c            
$ git checkout hello.c            

2
关于Accurev所使用的继承模式:当GIT用户查看git-flow(请参见:http://github.com/nvie/gitflowhttp://jeffkreeftmeijer.com/2010/why-arent-you-using-git-flow/)时,他们可能会完全“理解”整个模型。
这种GIT分支模型(手动/使用git-flow工具)或多或少地可以做到Accurev自动并且有着极好的GUI支持的一切功能。
因此,似乎GIT可以做到Accurev所能做到的。由于我从未真正日复一日地使用过git/git-flow,所以我无法确切地说它如何运作,但这看起来很有前途。(除了适当的GUI支持之外)

2
我会尝试回答您的问题。(需要说明的是,我没有使用过GIT,只是阅读了相关资料,如果以下内容有误,请纠正我)
“你能列举出必须使用继承的情况吗?”
我不会说这是必须的,因为您可以用手头的工具解决问题,这可能对您的环境是一个有效的解决方案。我想这更多是流程而不是工具本身的问题。确保您的流程连贯,并且允许您返回到任何中间步骤/状态以进行重现是目标,而该工具使您能够尽可能轻松地运行您的流程和SCMP。
我能看到这种“继承”行为很方便,并利用配置规范的功能的一个场景是当您希望将您的一组“隔离”的更改映射到任务(devtask、CR、SR或定义更改集的任何其他内容)时。
使用这种组合允许您保持开发分支的清洁,并仍然使用代码的不同组合(使用组合),并且在整个任务的生命周期中仅有与任务相关的内容隔离在一个分支中,直到集成阶段。
作为纯粹主义者,必须提交/合并/变基,只为了有一个“定义的起点”,我想它会“污染”您的分支,并且您最终将在您的分支/更改集中有您的更改+其他更改。
何时/何地使用这种隔离很有用?下面的观点可能只对追求CMM和一些ISO认证的公司有意义,对其他类型的公司或OSS可能没有兴趣。
- 很挑剔,您可能希望准确计算与单个开发人员对应的更改集的代码行数(添加/修改/删除),稍后用作代码和工作量估计的一个输入。 - 在不同阶段审查代码可能更容易,因为只有您的代码在单个分支中(而不是与其他更改粘合在一起)。
在一些大型项目中,有多个团队和500多名开发人员同时在同一个基础代码上工作(其中图形单个元素版本树看起来像是一个混乱的交错网,每个大客户都有一个或每种技术都有一个),使用深度组合的大型配置规范使得这些人能够无缝地适应不同的目的使用相同的产品/系统(基础代码)。使用此配置规范,动态地为每个团队或子团队提供不同的视图,以满足其需求,并从他们需要分支的位置开始(在多种情况下级联),而无需创建中间集成分支,或者不断地合并和重新定位所有需要开始的位。来自相同任务/目的的代码分支具有不同的标签,但有意义。(您可以在此处争论SCM的“已知基线”原则,但在编写的SCM计划中考虑到的简单标签已经完成了工作)
我猜这个问题可能可以用GIT解决(我想是非动态方式),但我很难想象没有这种“继承”行为。
我想VonC提到的“如果分支,则所有文件将从同一唯一起点分支”的观点在这里被打破了,但除了在SCMP上有很好的记录外,我记得有很强的业务原因要以这种方式进行。
是的,构建我上面提到的配置规范并不是免费的,在开始时,SCM后面有4-5个高薪人员,但后来通过自动化脚本减少了这些人员,这些脚本会询问您在标签/分支/功能方面想要什么,并为您编写CS。
所以,只有当您的项目足够大/复杂(并且您可以负担得起整个项目期间的SC经理:))时,您才会开始考虑是否需要“继承”行为或真正多功能的工具,否则您将直接使用一个免费的工具,并已经照顾好了您的SCM的一致性。...但是可能还有其他因素在SCM工具上可能使您坚持选择其中之一...继续阅读...
一些侧面说明,可能与主题无关,但我想在某些情况下需要考虑我的情况。
我必须在这里补充一点,我们使用的是“老牌CC”,而不是UCM。完全同意VonC所说的“好的方法论可以引导灵活性朝着更一致的配置方向发展”。好处在于CC非常灵活,您可以找到(不是没有努力)一个良好的方式,使事物在其他SCM中可能是免费的情况下也能保持一致。
但例如,在这里(以及我与CC合作的其他地方),对于C/C++项目,我们无法承受没有“winkin”功能(重用派生对象)的代价,这会减少编译时间数倍。可以说,拥有更好的设计、更解耦的代码和优化Makefiles可以减少编译整个项目的需求,但有些情况下,您需要每天多次编译整个项目,并且共享DO可以节省大量时间/金钱。
现在我们尽可能使用尽可能多的免费工具,如果我们能找到一个实现“winkin”功能的更便宜或免费的工具,我认为我们将摆脱CC。
我想以Paul提到的一些内容结束,“不同的工具对于不同的目的更好”,但我要补充的是,通过有条理的过程并且不牺牲可再现性,可以摆脱某些工具的限制,这是SCM的关键点。
最后,我想说答案取决于您的“问题”,正在运行的SDLC,您的SCM流程以及是否有任何额外的功能(如winkin),这在您的环境中可能很有用。
以上仅供参考。

+1。但是对于涉及大约150名开发人员的大型项目,我发现基于UCM配置所做的简化比复杂的配置规范更容易处理。 - VonC
只有约25个开发人员仍在使用普通的CC,但我已经编写了视图和配置规范的脚本,包括重定位和集成,使其对用户透明。这里每个项目需要大约1个每周发布的版本,所以他们不能等待集成阶段,并且需要在日常基础上“重定位”他们的代码,然后再进入INT阶段。为了满足这个要求,同时仍然保留每个任务的更改分支并避免复制合并,我在配置规范中使用了一级“级联”加上一些属性和标签技巧。 - FedeN
当我将这个适当地记录下来时,我会在某个地方发布它。它将老旧的DEV-INT-RELEASE架构与一些更敏捷的概念混合在一起。 - FedeN
+1 对于有见地的评论。我认为一切都取决于您选择的隔离类型。对于 git/mercurial,可以轻松地按任务、按特性或按技术等分支进行隔离(在 fork 中还可以按开发人员进行分支)。其他一些系统在使用多个分支时会浪费很多空间和速度。(可能有点脱离上下文,但我必须填写文本框)。 - Bogdan Maxim

2
理论方面不谈,从我在商业生产环境中使用AccuRev多年的经验来看,这是一种显然的实际应用:只要子流与仍在开发中的祖先流没有分歧太大,继承模型就非常有效。当继承流差异过大时,它就会失效。
继承(后续版本作为早期版本的子版本)允许祖先流的更改在子流中自动生效,无需任何人操作(除非需要合并,在这种情况下,它显示为深度重叠,这是很好的)。听起来很棒,实际上也确实如此,前提是所有相关流都相对类似。我们将该模型用于给定生产发布以下的热修复和服务包级别流。(对我们来说,实际上有点更加复杂,但那是一般思路。)
生产发布并行,没有继承,而是具有每个生产发布下面的热修复和服务包子流。开始一个新的发布意味着创建一个新的发布级别流,并手动将最近的先前发布的维护流中的所有内容推送到其中。此后,适用于较晚版本的早期版本的更改必须手动推送到每个版本中,需要更多的工作,但允许更大的控制。
我们最初在所有发布版本中都使用了继承模型,其中后续版本是早期版本的子版本。这在一段时间内运作良好,但随着时间的推移变得难以管理。跨版本的重大架构差异使不可避免地继承变得不可取。是的,您可以在中间放置快照来阻止继承,但然后所有更改都必须手动推送,而父级-快照-子级和并行非继承流之间唯一的真正区别是整个图形流视图不断向下和向右推,这是一个麻烦事。
AccuRev的一个非常好的特点是,您始终有这个选择。这不是您的SCM程序架构的固有限制。

有趣的是,RTC(Rational Team Concert,其中包括一些ClearCase UCM概念)在Streams上完全并行化,而这些Streams之间没有任何继承关系。您可以定义一个Streams列表,并对每个Streams进行任何操作。 - VonC

1

ClearCase没有MultiSite,是一个单一的代码库,但Git是分布式的。ClearCase在文件级别上提交,而Git在代码库级别上提交。(正如其他帖子中指出的那样,这最后一个差异意味着原始问题基于误解。)

如果我们讨论的是这些差异,那么我认为“线性”与“DAG”是区分这些SCM系统的一种令人困惑的方式。在ClearCase中,文件的所有版本都被称为文件的版本“树”,但实际上它是一个有向无环图!与Git的真正区别在于,ClearCase的DAG存在于每个文件中。因此,我认为将ClearCase称为非DAG,将Git称为DAG是具有误导性的。

(顺便说一句,ClearCase以类似于其文件的方式对其目录进行版本控制-但这是另一回事。)


0

我不确定你是否在询问什么,但你正在展示Accurev流与Git(或SVN)分支是不同的工具。(我不了解Clearcase。)

例如,使用Accurev,你被“强制”使用某些工作流程,这给你提供了一个可审计的更改历史记录,在Git中不支持。Accurev的继承使某些工作流程更有效,而其他工作流程则不可能。

使用Git,你可以将探索性编码隔离在本地存储库或功能分支中,这在Accurev中不会得到很好的支持。

不同的工具适用于不同的目的;询问每个工具适用于哪些方面是有用的。


保罗,不,我不是在试图演示,而是在询问。我的意思是,您能否确切地解释只能使用Accurev完成哪些工作流程。我习惯于使用Clearcase / Accurev,但是后来了解到GIT并且非常喜欢它,然后我发现你不能在GIT中像加载特定版本的特定文件到您的“工作区”一样做事情,而是仅限于特定提交,也不能继承更改(始终需要新提交)。 我的问题是:好的,GIT无法做到这一点,但您会失去什么吗? - jbhope
2
在AccuRev中,我们有时会将新功能作为子流来开发。当该功能完成后,其中的所有内容都将向上推广到父级。通常情况下,我们会隐藏子功能流,因为不会再在那里进行任何工作。 - enigment

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接