Git和Mercurial-比较和对比


520

一段时间以来,我一直在为个人项目使用subversion。

我越来越多地听到有关Git和Mercurial以及DVCS的很棒的事情。

我想稍微谈一下整个DVCS,但是我对这两种选择都不是很熟悉。

Mercurial和Git之间有什么区别?

注意:我不是要找出哪一个是“最佳”,甚至我应该从哪一个开始。我主要是在寻找相似之处和不同之处的关键领域,因为我很想知道它们在实现和理念上如何不同。



Answers:


451

免责声明: 我使用Git,关注git邮件列表上的Git开发,甚至对Git有所贡献(主要是gitweb)。我从文档中了解到Mercurial,从FreeNode上#revctrl IRC频道的讨论中也了解到Mercurial。

感谢所有在#mercurial IRC频道上的人,他们为这篇文章提供了有关Mercurial的帮助



摘要

在这里最好有一些表语法,例如PHPMarkdown / MultiMarkdown / Markdown的Maruku扩展

  • 仓库结构: Mercurial不允许章鱼合并(有两个以上的父母),也不能标记未提交的对象。
  • 标签: Mercurial .hgtags对每个存储库标签使用带有特殊规则的版本化文件,并且还支持;中的本地标签.hg/localtags。在Git中,标记是驻留在refs/tags/名称空间中的ref ,默认情况下会在提取时自动跟踪,并且需要显式推送。
  • 分支机构:在Mercurial中,基本工作流基于匿名负责人;Git使用轻量级的命名分支,并具有跟随远程存储库中的分支的特殊类型的分支(远程跟踪分支)。
  • 修订版本的命名和范围: Mercurial提供修订版本号(本地到版本库),并基于此本地编号(相对于修订版本,即从提示(即当前分支)开始计数)和修订版本范围建立基础;Git提供了一种相对于分支尖端引用修订的方法,并且修订范围是拓扑的(基于修订图)
  • Mercurial使用重命名跟踪,而Git使用重命名检测来处理文件重命名
  • 网络: Mercurial支持SSH和HTTP“智能”协议以及静态HTTP协议;现代的Git支持SSH,HTTP和GIT“智能”协议以及HTTP(S)“哑”协议。两者都支持用于离线传输的捆绑文件。
  • Mercurial使用扩展(插件)和已建立的API;Git具有脚本功能和既定格式。

Mercurial与Git有一些不同之处,但其他一些因素也使它们相似。两个项目都相互借鉴。例如hg bisect,Mercurial(以前为bisect扩展名)中的git bisect命令受Git中的命令启发,而的想法git bundle则受启发hg bundle

仓库结构,存储修订

在Git中,其对象数据库中有四种类型的对象:包含文件内容的blob对象,存储目录结构的分层对象,包括目录名称和文件许可权的相关部分(文件的可执行许可权,是符号链接) ,包含作者信息的提交对象,指向由提交表示的修订版存储库状态快照的指针(通过项目顶层目录的树对象)以及对零个或多个父提交的引用,以及标记引用其他对象并且可以使用PGP / GPG签名。

Git使用两种存储对象的方式:松散格式,其中每个对象存储在一个单独的文件中(那些文件只写一次,并且从未修改过);打包格式,其中许多对象以增量压缩的方式存储在一个文件中。操作的原子性是由以下事实提供的:在写入对象之后(通过原子方式使用create +重命名技巧)写入对新对象的引用。

Git存储库需要使用进行定期维护git gc(以减少磁盘空间并提高性能),尽管如今Git是自动执行的。(此方法可更好地压缩存储库。)

Mercurial(据我所知)将文件的历史记录存储在文件日志中(我认为,它们一起包含重命名跟踪和一些帮助程序信息等额外的元数据);它使用称为清单的平面结构来存储目录结构,并使用名为changelog的结构来存储有关变更集(修订)的信息,包括提交消息和零个,一或两个父级。

Mercurial使用事务日志来提供操作的原子性,并依靠在操作失败或中断后截断文件来进行清理。修订日志仅附加。

查看Git与Mercurial中的存储库结构,可以看到Git更像对象数据库(或内容寻址文件系统),而Mercurial更像传统的固定字段关系数据库。

区别:
在Git中,对象形成一个层次结构;Mercurial 清单文件中的文件是平面结构。在Git blob对象中,存储文件内容的一种版本。在Mercurial文件日志中,存储单个文件的全部历史记录(如果此处未考虑重命名的任何复杂性)。这意味着在不同的操作区域中,Git会比Mercurial快,其他所有条件都相同(例如合并或显示项目的历史记录),在Mercurial的区域中,Git会比Git更快(例如应用补丁或显示单个文件的历史记录)。对于最终用户,此问题可能并不重要。

由于Mercurial的变更日志结构的固定记录结构,Mercurial中的提交最多只能有两个父级;Git中的提交可以有两个以上的父级(所谓的“章鱼合并”)。虽然您可以(理论上)用一系列的双亲合并替换章鱼合并,但这在Mercurial和Git存储库之间转换时可能会导致复杂化。

据我所知,Mercurial没有来自Git 的带注释的标签(标签对象)。带注释的标签的一种特殊情况是签名标签(带有PGP / GPG签名);可以使用GpgExtension完成与Mercurial等效的工作,该扩展名将与Mercurial一起分发。您不能像在Git中那样在Mercurial中标记未提交的对象,但这并不是很重要,我认为(某些git存储库使用标记的blob分发公共PGP密钥来验证已签名的标记)。

参考:分支和标签

在Git中,引用(分支,远程跟踪分支和标签)位于提交的DAG外部(应如此)。refs/heads/命名空间(本地分支)中的引用指向提交,通常通过“ git commit”进行更新;他们指向分支的尖端(头),这就是为什么这样的名字。refs/remotes/<remotename>/命名空间中的引用(远程跟踪分支)指向提交,跟随远程存储库中的分支<remotename>,并通过“ git fetch”或等效更新。refs/tags/命名空间(标签)中的引用通常指向提交(轻量级标签)或标签对象(带注释的和签名的标签),而不是要更改。

标签

在Mercurial中,您可以使用标签为修订版赋予永久名称;标签的存储方式与忽略模式相似。这意味着全局可见的标记存储在.hgtags存储库中受版本控制的文件中。这有两个结果:首先,Mercurial必须对该文件使用特殊规则以获取所有标签的当前列表并更新该文件(例如,它读取文件的最新提交修订版,而不是当前签出的版本);第二,您必须对此文件进行更改,以使新标签对其他用户/其他存储库可见(据我所知)。

Mercurial还支持存储在中的本地标签hg/localtags其他人看不到(当然也不能转让)

在Git中,标记是对存储在refs/tags/名称空间中的其他对象(通常是标记对象,依次指向提交)的固定(常量)命名引用。默认情况下,当获取或推送一组修订时,git会自动获取或推送指向正在获取或推送的修订的标记。不过,您可以在一定程度上控制获取或推送的标签

Git对轻量级标签(直接指向提交)和带注释的标签(指向包含对象消息的标签对象,这些消息可选地包含PGP签名,进而指向提交)的对待略有不同,例如,默认情况下,它在描述时仅考虑带注释的标签使用“ git describe”提交。

Git在Mercurial中没有严格等同于本地标签的地方。尽管如此,git最佳实践还是建议您设置单独的公共裸存储库,将准备好的更改推送到该存储库中,并从其他存储库进行克隆和获取。这意味着您不推送的标签(和分支)是存储库专用的。在另一方面,你也可以使用除命名空间的headsremotes或者tags,例如local-tags用于局部变量。

个人观点:我认为标签应位于修订图之外,因为它们在修订图之外(它们是修订图的指针)。标签应该是非版本化的,但可以转移。Mercurial选择使用与忽略文件类似的机制,这意味着它要么必须.hgtags特别对待(文件树中的文件是可转让的,但通常是版本化的),或者只有局部标签(.hg/localtags非版本化,但不可转让)。

分行

在Git中,本地分支(分支尖端或分支头)是对提交的命名引用,可以在其中增长新的提交。分支还可以表示活跃的开发线,即所有可从分支尖端到达的提交。本地分支位于refs/heads/名称空间中,因此“ master”分支的完全限定名称为“ refs / heads / master”。

Git中的当前分支(意味着已签出的分支,以及将要进行新提交的分支)是由HEAD ref引用的分支。一个HEAD可以直接指向一个提交,而不是符号引用。在匿名未命名分支上的这种情况称为分离HEAD(“ git分支”显示您在“(无分支)”上)。

在Mercurial中,有匿名分支(分支头),并且可以使用书签(通过书签扩展名)。这样的书签分支纯粹是本地的,并且这些名称(在1.6版之前)不能使用Mercurial进行传输。您可以使用rsync或scp将.hg/bookmarks文件复制到远程存储库。您还可以使用hg id -r <bookmark> <url>获取书签的当前提示的修订ID。

由于1.6书签可以被推/拉。该BookmarksExtension页面有一个节正在与远程仓库。区别在于Mercurial书签的名称是全局的,而Git中的'remote'定义也描述了分支名称从远程存储库中的名称到本地远程跟踪分支名称的映射。例如,refs/heads/*:refs/remotes/origin/*映射意味着可以在“源/主”远程跟踪分支(“ refs / remotes / origin / master”)的远程存储库中找到“ master”分支(“ refs / heads / master”)的状态。

Mercurial也有所谓的命名分支,其中分支名称嵌入在提交中(在变更集中)。这样的名称是全局的(在获取时转移)。这些分支名称将永久记录为变更集元数据的一部分。使用现代Mercurial,您可以关闭“命名分支”并停止记录分支名称。在这种机制中,动态计算分支的尖端。

在我看来,Mercurial的“命名分支”应该称为提交标签,因为它就是它们。在某些情况下,“命名分支”可以具有多个提示(多个无子提交),并且还可以包含修订图的几个不相交的部分。

在Git中没有相当于Mercurial的“嵌入式分支机构”。此外,Git的理念是,虽然可以说分支包含某个提交,但这并不意味着一个提交属于某个分支。

请注意,Mercurial文档仍然建议至少对寿命长的分支(每个存储库工作流为单个分支)使用独立的克隆(独立的存储库),也称为通过克隆进行分支

分行推

默认情况下,Mercurial会全力以赴。如果要推动单个分支(单头),则必须指定要推动的分支的尖端修订版。您可以通过其修订版本号(对存储库本地),修订版本标识符,书签名称(对存储库本地,不被传输)或嵌入式分支名称(命名分支)来指定分支提示。

据我了解,如果您推送一系列修订版本,其中包含在Mercurial术语中标记为位于“命名分支”上的提交,则将在您推送到的存储库中使用此“命名分支”。这意味着此类嵌入式分支(“命名分支”)的名称是全局的(相对于给定存储库/项目的克隆)。

默认情况下(取决于push.default配置变量),“ git push”或“ git push < remote >” Git会推送匹配的分支,即,只有与之等效的本地分支已经存在于您推送到的远程存储库中。您可以使用--all选项git-push(“ git push --all”)来推送所有分支,可以使用“ git push < remote > < branch >”来推送给定的单个分支,并且可以使用“ git push < 远程 > HEAD”以推送当前分支

以上所有假设均假设未配置Git,哪个分支通过remote.<remotename>.push 配置变量进行推送。

提取中的分支

注意:这里我使用Git术语,其中“获取”表示从远程存储库下载更改,而无需将这些更改与本地工作集成在一起。这就是“ git fetch”和“ hg pull”的作用。

如果我理解正确,默认情况下,Mercurial会从远程存储库获取所有文件,但是您可以指定要通过“ hg pull --rev <rev> <url>”或“ hg pull <url>#<rev>”获取的分支,以获取单个branch。您可以使用修订标识符,“命名分支”名称(嵌入在changelog中的分支)或书签名称来指定<rev>。但是,书签名称(至少当前)不会被转移。您获得的所有“命名分支”修订都属于转让。“ hg pull”存储以匿名,未命名的头的形式获取的分支提示。

默认情况下,在Git中(对于由“ git clone”创建的“源”远程,以及对于使用“ git remote add”创建的远程),“ git fetch”(或“ git fetch <remote>”)从远程存储库(从名称空间)获取所有分支refs/heads/,并将其存储在refs/remotes/命名空间。例如,这意味着远程“起源”中名为“ master”(全名:“ refs / heads / master”)的分支将被存储(保存)为“ origin / master” 远程跟踪分支(全名:“ refs /遥控器/原点/主机”)。

您可以通过使用- 在Git中获取单个分支git fetch <remote> <branch> -Git会将请求的分支存储在FETCH_HEAD中,这类似于Mercurial未命名头。

这些只是具有强大refspec Git语法的默认情况的示例:使用refspecs,您可以指定和/或配置要获取的分支以及将其存储在何处。例如,默认的“获取所有分支”情况用“ + refs / heads / *:refs / remotes / origin / *”通配符refspec表示,“ fetch single branch”是“ refs / heads / <branch>:”的简写。 。Refspec用于将远程存储库中的分支(ref)名称映射到本地ref名称。但是您不必(很)了解refspec就能与Git有效配合(主要感谢“ git remote”命令)。

个人意见:我个人认为Mercurial中的“命名分支”(分支名称嵌入在变更集元数据中)是带有其全局命名空间的误导设计,尤其是对于分布式版本控制系统。例如,假设爱丽丝和鲍勃在其存储库中都有名为“ for-joe”的“命名分支”,这两个分支没有共同之处。但是,在Joe的存储库中,这两个分支将被视为单个分支。因此,您以某种方式提出了防止分支名称冲突的约定。这对于Git来说不是问题,在Joe的存储库中,来自Alice的“ for-joe”分支为“ alice / for-joe”,而来自Bob的分支为“ bob / for-joe”。

Mercurial的“书签分支”目前缺乏核心分发机制。

差异:
这是Mercurial和Git之间的主要差异之一,就像詹姆斯·伍德亚特史蒂夫·洛斯在回答中所说的那样。默认情况下,Mercurial使用匿名的轻量级代码行,在其术语中称为“ heads”。Git使用轻量级的命名分支,并使用注入式映射将远程存储库中的分支名称映射到远程跟踪分支的名称。Git“强迫”您命名分支(好,单个未命名分支除外,这种情况称为分离的HEAD),但是我认为这对繁重的工作流(例如主题分支工作流)更有效,这意味着单个存储库范式中的多个分支。

命名修订

在Git中,有多种命名版本的方法(例如,在git rev-parse联机帮助页中进行了描述):

  • 完整的SHA1对象名称(40字节的十六进制字符串),或在存储库中唯一的子字符串
  • 符号引用名称,例如“ master”(指“ master”分支),“ v1.5.0”(指标签)或“ origin / next”(指远程跟踪分支)
  • 后缀^修订版参数表示提交对象^n的第一个父级,表示合并提交的第n个父级。~n修订参数的后缀表示在第一个父级直线中提交的第n个祖先。可以组合这些后缀,以按照符号引用的路径形成修订说明符,例如“ pu〜3 ^ 2〜3”
  • 输出“ git describe”,即最接近的标签,可以选择后面跟一个破折号和多个提交,然后跟一个破折号,一个“ g”和一个缩写的对象名,例如“ v1.6.5.1-75- g5bf8097”。

也有涉及reflog的修订说明符,此处未提及。在Git中,每个对象,无论是提交,标记,树还是Blob,都有其SHA-1标识符;有特殊的语法,例如“ next:Documentation”或“ next:README”,用于指代指定版本的树(目录)或blob(文件内容)。

Mercurial还具有多种命名变更集的方式(例如,在hg联机帮助页中进行了描述):

  • 普通整数被视为修订号。需要记住,修订号对于给定的存储库本地的;在其他存储库中,它们可以不同。
  • 负整数被视为距笔尖的连续偏移量,-1表示笔尖,-2表示笔尖之前的修订版,依此类推。它们也是存储库本地的
  • 唯一的修订标识符(40位十六进制字符串)或其唯一前缀。
  • 标记名称(与给定修订版本关联的符号名称)或书签名称(扩展名:与给定的标头相关联的符号名称,在存储库本地),或“命名分支”(提交标签;由“命名分支”给出的修订版本)给定提交标签的所有提交的提示(无子提交),如果有多个这样的提示,则修订版本号最大)
  • 保留名称“ tip”是一个特殊标记,始终标识最新修订。
  • 保留名称“ null”表示空修订。
  • 保留名称“。” 指示工作目录的父目录。

差异
正如您所看到的,比较上面的列表,Mercurial提供的修订版本号在本地存储库中,而Git没有。另一方面,Mercurial仅提供相对于存储库本地的“技巧”(当前分支)的相对偏移量(至少没有ParentrevspecExtension),而Git允许指定任何技巧之后的任何提交。

最新的版本在Git中被命名为HEAD,在Mercurial中被命名为“ tip”。Git中没有空修订。Mercurial和Git都可以有很多根(可以有多个不带父项的提交;这通常是以前分开的项目加入的结果)。

另请参见: Elijah的博客(newren's)上的许多不同版本的修订说明文章。

个人意见:我认为修订版本号被高估了(至少对于分布式开发和/或非线性/分支历史而言)。首先,对于分布式版本控制系统,它们必须位于存储库本地,或者要求以特殊方式将某些存储库视为中央编号机构。其次,具有较长历史记录的较大项目的修订版本数可以在5位数字范围内,因此相对于缩短到6-7个字符的修订版本标识符而言,它们仅具有微小的优势,这意味着严格的排序,而修订版本仅是部分排序的(我的意思是,修订版n和n + 1不必是父母和孩子)。

修订范围

在Git中,修订范围是拓扑。通常的A..B语法,对于线性历史记录来说,表示修订范围从A开始(但不包括A),到B为止(即,范围从下面打开),是的简写形式(“语法糖”)^A B,对于历史记录遍历命令来说,它表示所有提交从B可到达的对象,但不包括从A可以到达的对象。这意味着A..B即使A不是B的祖先,范围的行为也是完全可预测的(并且非常有用):A..B意味着从A和B的祖先的修订范围(合并基数) )到修订版B。

在Mercurial中,修订范围基于修订编号范围。范围是使用A:B语法指定的,与Git范围相反,它用作封闭间隔。同样,范围B:A是相反的范围A:B,在Git中不是这种情况(但请参见下面有关A...B语法的注释)。但是,这种简单性是有代价的:只有当A是B的祖先,反之亦然,即具有线性历史时,修订范围A:B才有意义。否则(我想)范围是不可预测的,并且结果对于存储库是本地的(因为修订号对于存储库是本地的)。

这是由Mercurial 1.6修复的,它具有新的拓扑修订范围,其中“ A..B”(或“ A :: B”)被理解为既是X的后代又是Y的祖先的变更集。 ,我想相当于Git中的“ --ancestry-path A..B”。

Git还具有A...B修订版本的对称差异的表示法;它表示A B --not $(git merge-base A B),表示所有可从A或B到达的提交,但不包括从两者均可到达的所有提交(可从共同祖先到达)。

重命名

Mercurial使用重命名跟踪来处理文件重命名。这意味着有关文件已重命名的事实的信息将在提交时保存。在水银这个信息被保存在“增强的差异”形式filelog(文件revlog)元数据。其结果是您必须使用hg rename/ hg mv...,或者需要记住运行hg addremove以进行基于相似性的重命名检测。

Git在版本控制系统中是唯一的,因为它使用重命名检测来处理文件重命名。这意味着在需要时检测到文件已被重命名的事实:进行合并或显示差异(如果请求/配置)。这样的优点是可以改进重命名检测算法,并且在提交时不会冻结。

--follow显示单个文件的历史记录时,Git和Mercurial都需要使用选项跟随重命名。在git blame/中显示文件的逐行历史记录时,两者都可以遵循重命名hg annotate

在Git中,git blame命令可以跟随代码移动,也可以将代码从一个文件移动(或复制)到另一个文件,即使代码移动不是有益的文件重命名的一部分。 据我所知,此功能是Git独有的(在撰写本文时,2009年10月)。

网络协议

Mercurial和Git都支持从同一文件系统上的存储库获取和推送到同一文件系统,其中存储库URL只是存储库的文件系统路径。两者都还支持从捆绑文件中提取。

Mercurial支持通过SSH和HTTP协议进行获取和推送。对于SSH,需要在目标计算机上有一个可访问的Shell帐户,以及已安装/可用的hg副本。对于HTTP访问,hg-serve需要运行或Mercurial CGI脚本,并且需要在服务器计算机上安装Mercurial。

Git支持两种用于访问远程存储库的协议:

  • “智能”协议(包括通过SSH和自定义git://协议(由git-daemon)访问)要求在服务器上安装git。这些协议中的交换包括客户机和服务器就它们共有的对象进行协商,然后生成并发送一个包文件。Modern Git包括对“智能” HTTP协议的支持。
  • “哑”协议(包括HTTP和FTP(仅用于获取)和HTTPS(用于通过WebDAV进行推送))不需要在服务器上安装git,但是它们确实要求存储库包含由git update-server-info(通常从挂钩运行)生成的额外信息。)。交换包括客户端遍历提交链并根据需要下载松散的对象和packfile。不利之处在于,它下载的内容超过了严格要求的数量(例如,在极端情况下,即使只有一个packfile,即使仅获取几个修订版本,它也将全部下载),并且它可能需要许多连接才能完成。

扩展:脚本性与扩展性(插件)

Mercurial用Python实现,并用C编写了一些核心代码以提高性能。它提供用于编写扩展程序(插件)的API,作为添加其他功能的一种方式。与Mercurial一起分发的扩展中提供了某些功能,例如“书签分支”或签名修订,需要将其打开。

Git是用CPerlShell脚本实现的。Git提供了许多适用于脚本的底层命令(管道)。引入新功能的常用方法是将其编写为Perl或Shell脚本,并在用户界面稳定后再用C对其进行重写以提高性能,可移植性,并且在Shell脚本避免出现极端情况的情况下(此过程称为buildinification)。

Git依赖并基于[存储库]格式和[网络]协议构建。除了语言绑定以外,还有(部分或全部)其他语言对Git的重新实现(其中一些是部分重新实现,并且部分围绕git命令进行包装):JGit(Java,由EGit,Eclipse Git插件使用),Grit(Ruby) ,Dulwich(Python),git#(C#)。


TL; DR


32
可以添加的是,hg会非常努力地阻止历史记录重写(只能通过扩展名:mq,histedit,rebase来完成),而git则是开箱即用的(看起来像社区的一部分)甚至鼓励它)。
09年

80
我认为“重写历史记录”是不必要的消极声音。什么鼓励Git是人们考虑他们出版的历史。其他人需要消耗那个历史。没有人(甚至不是您)对您所有的“糟糕,忘记文件”提交都没有兴趣。也没有人关心您在使用新功能时跟踪上游分支时所经历的一系列入站合并。这种东西使历史(及相关工具)更加难以理解,并且毫无价值。
达斯汀2009年

5
@Jakub:命名分支是git中不存在的东西。它只是cset描述中的一个字段(这是历史记录的一部分,因此除非更改哈希等,否则它是不可变的)。像git分支之类的东西是书签(“命名头”),但是它们当前不可远程转让(在拉动时,您不会导入远程书签)。 stevelosh.com/blog/entry/2009/8/30/…解释得很好。
tonfa

28
“ Mercurial最初每个存储库工作流仅支持一个分支,它显示了。” 嗯不 Mercurial最初不支持命名分支,但是您始终可以在单个回购协议中拥有尽可能多的匿名分支。与git对比,这使匿名分支变得非常痛苦。如果您想完成任何事情(并且避免收集工作垃圾),则几乎必须为每个小分支想到一个名称。
2009年

17
@SteveLosh:您似乎认为Mercurial中有许多匿名分支是一件好事,但对我来说,这似乎很可怕。你怎么把他们分开?而且您似乎认为在Git中命名分支是一个巨大的困难,但是如果您有创建分支的目的,那么您就有一个现成的名称。如果没有目的,请不要分支。我看不到Mercurial如何在这里提供任何好处。我只看到痛苦和困惑。
iconoclast 2012年

57

我想您可以通过整理这两个视频来了解这些系统的相似之处或不同之处:

Git上的Linus Torvalds(http://www.youtube.com/watch?v=4XpnKHJAok8
Bryan O'Sullivan在Mercurial上(http://www.youtube.com/watch?v=JExtkqzEoHY

两者在设计上非常相似,但在实现上却非常不同。

我用水银。就我所了解的Git而言,git的主要不同之处在于它跟踪文件的内容而不是文件本身。Linus说,如果您将一个函数从一个文件移动到另一个文件,Git会告诉您该单个函数在整个移动过程中的历史。

他们还说git比HTTP慢一些,但是它具有自己的网络协议和服务器。

Git作为SVN胖客户端比Mercurial更好。您可以拉动SVN服务器。该功能仍在Mercurial中开发中

Mercurial和Git都有非常好的Web托管解决方案(BitBucket和GitHub),但是Google Code仅支持Mercurial。顺便说一下,他们对决定支持哪一个产品的Mercurial和Git进行了非常详细的比较(http://code.google.com/p/support/wiki/DVCSAnalysis)。它有很多很好的信息。


8
我建议您阅读该Google代码页上的所有评论。信息确实有些偏见,与我的经验不符。我喜欢汞,并广泛使用了一年左右。我现在几乎只使用git。我需要完成使git变得容易而使hg变得几乎不可能的事情(尽管有些人喜欢通过“复杂化”来称呼它。)基本git和base hg一样容易。
达斯汀

11
达斯汀(Dustin),也许列出一些“ git easy,hg没那么多”的案例?
Gregg Lind)

1
@knittl不,不是。主要是因为git缺少智能的http协议(大多数Google前端都是基于http的),因此部署它们会很痛苦。
tonfa

2
@tonfa:目前正在开发针对Git的智能HTTP协议(如:git邮件列表中有补丁,它们位于git.git存储库中的“ pu” =建议的更新分支中)。
2009年

4
到目前为止,Google Code还支持Git。
AndrejKirejeŭ2012年

30

不久前,我写了一篇有关Mercurial分支模型的博客文章,其中包括与git分支模型的比较。也许您会发现它很有趣:http : //stevelosh.com/blog/entry/2009/8/30/a-guide-to-branching-in-mercurial/


@Steve Losh:我想评论一下此博客条目(关于未命名的分支,也称为分离的HEAD,以及关于git-fetch获取所有分支的信息,而不是一个),但我收到500台服务器错误。
JakubNarębski09年

1
@JakubNarębski我敢打赌,问题是您姓名中的非ASCII字符。我敢肯定,我在另一个站点上也遇到了同样的问题,结果发现Unicode上的Python Askimet绑定问题。我会看一看。
2009年

@Steve Losh:感谢您提供信息,在“取消编码”我的名字后,我可以发表评论。关于Mercurial分支的很好描述(但我仍然认为它是劣等的;-)
JakubNarębski09年

@SteveLosh我鼓励您将此答案扩展为对汞的更完整的评论。现在,最不幸的是,最主要的答案是git的广告,因为它的作者尚未广泛使用汞,也不了解如何有效地使用它。可以这么说,提供另一个观点很好地回答了另一个问题。
沃伦·露

25

我经常使用两者。功能上的主要区别在于Git和Mercurial名称在存储库中分支的方式。使用Mercurial,可以克隆分支名称,并将其与变更集一起拉出。当您将更改添加到Mercurial中的新分支并推送到另一个存储库时,分支名称将同时推送。因此,分支名称在Mercurial中或多或少是全局的,您必须使用Bookmark扩展名来具有仅本地的轻量名称(如果您需要它们;默认情况下,Mercurial使用匿名轻量代码行,在其术语中是称为“元首”)。在Git中,分支名称及其到远程分支的内在映射存储在本地,您必须显式管理它们,这意味着知道如何执行。

正如其他人会在这里指出的那样,有很多细微的差别。分支机构是最大的区别。


2
也看到这个帖子了一个很好的解释关于四种分支的水银:stevelosh.com/blog/entry/2009/8/30/...
马丁盖斯勒


11

Mercurial几乎完全用python编写。Git的核心是用C编写的(并且应该比Mercurial的要快),工具是用sh,perl,tcl编写的,并使用标准的GNU utils。因此,它需要将所有这些utils和解释器都带到不包含它们的系统中(例如Windows)。

两种支持都可以与SVN一起使用,尽管AFAIK svn对Windows上的git的支持已被破坏(也许我只是倒霉/ who,谁知道)。还有一些扩展允许在git和Mercurial之间进行互操作。

Mercurial具有很好的Visual Studio集成。上次我检查时,Git的插件正在运行,但运行非常缓慢。

它们的基本命令集非常相似(初始化,克隆,添加,状态,提交,推送,拉取等)。因此,基本工作流程将相同。另外,两者都有类似TortoiseSVN的客户端。

Mercurial的扩展名可以用python编写(毫不奇怪!),而git的扩展名可以以任何可执行形式(可执行二进制文件,shell脚本等)编写。有些扩展功能疯狂得厉害,例如git bisect


9
Mercurial核心也是用C编写的,以供FYI使用(但它可能比git小)。
09年

1
我在Windows上使用git-svn没有任何麻烦。那就是使用Cygwin(如果您问我,这是在Windows上使用git 的唯一正确方法)。不能代表msysgit。
丹·

@Dan Moulding:是的,我遇到了msysgit的问题。也许需要尝试一下cygwin port(我之前使用cygwin的经验很差,所以我避免了)。谢谢你的建议!
2009年

我个人不喜欢cygwin入侵注册表来存储用户数据。这是一种PITA,可使其在USB闪存盘上运行,并使其本地c:\驱动器副本保持同步,以便在我想以比USB闪存盘运行速度更快的速度运行。:-/
Chris K

1
我使用上述用于Visual Studio的Git插件,当前版本的性能良好。它使用命令行工具完成工作,因此我认为它不会在大型项目上显着降低性能。
斯图尔特·埃利斯

11

如果您需要良好的Windows支持,则可能更喜欢Mercurial。TortoiseHg(Windows资源管理器插件)设法为相当复杂的工具提供了易于使用的图形界面。作为此处的状态,您还将有一个Visual Studio插件。但是,上次尝试时,SVN界面在Windows上无法正常运行。

如果您不介意命令行界面,我建议您使用Git。不是出于技术原因,而是出于战略原因。混帐的采用率高。只要看看有多少著名的开源项目正在从cvs / svn转换到Mercurial,以及有多少正在转换到Git。查看与Mercurial托管相比,有git支持可以找到多少个代码/项目托管提供商。


如果您不喜欢使用命令行,也可以使用TortoiseGit。(但是需要安装msysgit。)
Ben James,

2
由于对Windows的大力支持,我们公司最终选择了git- 请查看Git Extensions。我有偏见,因为我现在是一名贡献者,但是当我们开始使用它时却不是。
Jacob Stanley

11

阅读完所有内容后,Mercurial变得更容易(我仍然相信,毕竟互联网社区都认为这是事实),当我开始与Git和Mercurial合作时,我觉得Git对我来说适应起来相对更简单(我开始从命令行使用Mercurial和TortoiseHg),主要是因为git命令是根据我适当命名的,并且数量较少。Mercurial对于执行不同工作的每个命令都有不同的命名,而Git命令可以根据情况具有多种用途(例如,checkout)。虽然Git那时更难,但现在几乎没有实质性差异。YMMV ..有了TortoiseHg这样的GUI客户端,使用Mercurial确实容易得多,而且我也不必记住那些令人困惑的命令。我不会详细说明同一操作的每个命令如何变化,但是这里有两个综合列表:1来自Mercurial自己的站点2来自Wikivs

╔═════════════════════════════╦════════════════════════════════════════════════════════════════════════════════════════════════╗
║           Git               ║                Mercurial                                                                       ║
╠═════════════════════════════╬════════════════════════════════════════════════════════════════════════════════════════════════╣
║ git pull                    ║ hg pull -u                                                                                     ║
║ git fetch                   ║ hg pull                                                                                        ║
║ git reset --hard            ║ hg up -C                                                                                       ║
║ git revert <commit>         ║ hg backout <cset>                                                                              ║
║ git add <new_file>          ║ hg add <new_file> (Only equivalent when <new_file> is not tracked.)                            ║
║ git add <file>              ║ Not necessary in Mercurial.                                                                    ║
║ git add -i                  ║ hg record                                                                                      ║
║ git commit -a               ║ hg commit                                                                                      ║
║ git commit --amend          ║ hg commit --amend                                                                              ║
║ git blame                   ║ hg blame or hg annotate                                                                        ║
║ git blame -C                ║ (closest equivalent): hg grep --all                                                            ║
║ git bisect                  ║ hg bisect                                                                                      ║
║ git rebase --interactive    ║ hg histedit <base cset> (Requires the HisteditExtension.)                                      ║
║ git stash                   ║ hg shelve (Requires the ShelveExtension or the AtticExtension.)                                ║
║ git merge                   ║ hg merge                                                                                       ║
║ git cherry-pick <commit>    ║ hg graft <cset>                                                                                ║
║ git rebase <upstream>       ║ hg rebase -d <cset> (Requires the RebaseExtension.)                                            ║
║ git format-patch <commits>  ║ hg email -r <csets> (Requires the PatchbombExtension.)                                         ║
║   and git send-mail         ║                                                                                                ║
║ git am <mbox>               ║ hg mimport -m <mbox> (Requires the MboxExtension and the MqExtension. Imports patches to mq.)  ║
║ git checkout HEAD           ║ hg update                                                                                      ║
║ git log -n                  ║ hg log --limit n                                                                               ║
║ git push                    ║ hg push                                                                                        ║
╚═════════════════════════════╩════════════════════════════════════════════════════════════════════════════════════════════════╝

Git在内部保存每个版本的提交文件的记录,而Hg只保存可能占用较小空间的变更集。与Hg相比,Git可以更轻松地更改历史记录,但是它又具有“讨厌它或喜欢它”功能。前者我喜欢Hg,后者则喜欢Git。

我在Hg中想念的是Git的子模块功能。Hg有subrepos,但这不完全是Git子模块。

两者之间的生态系统也可能影响一个人的选择:Git必须更受欢迎(但这很琐碎),Git具有GitHub,而Mercurial具有BitBucket,Mercurial具有TortoiseHg,我认为这对Git没有什么好处。

每个都有其优点和缺点,有了它们中的任何一个,您都不会失去。


8

不久前请查看Scott Chacon的帖子

我认为git以“更复杂”而著称,尽管根据我的经验,它并没有比需要的复杂。IMO,git的模型的方式更容易理解(标签包含提交(和指针,以零个或多个父提交)含有树包含斑点和其他树木......完成)。

不仅仅是我的经验,git不会比mercury更令人困惑。我建议再次阅读Scott Chacon的这篇博客文章


1
Mercurial模型实际上几乎是相同的:changelog指向清单,指向文件修订/ blob ...完成。如果您比较磁盘上的格式,则可能没有考虑到packs文件,而该文件比hg中的简单revlog格式更难解释。
2009年

好吧,这个简化的模型会忽略标记,这在hg中实际上是笨拙的(尽管我确实认为git tag有点令人困惑,因为它默认情况下不会创建标记对象)。对于两个具有大量文件名历史的项目,磁盘格式的成本特别高。
达斯汀2009年

1
我认为该模型不会忽略标记:在Mercurial中标记很简单-如您所知,它只是一个为SHA-1哈希命名的文件。无需担心标签如何在系统中四处流通:它们会随着推和拉一起移动。而且,如果存在标签冲突,那么解决它也是微不足道的:您像解决任何其他冲突一样解决它。毕竟,它只是文本文件中的一行。我认为此模型的简单性是一个非常好的功能。
Martin Geisler

Dustin:是的,用户经常对以下事实感到困惑:在.hgtags签出修订版1.0时看不到1.0标记。但是,您无需查看内部.hgtagshg tags仍然会列出所有标签。此外,这种行为是将标签存储在版本控制文件中的简单结果-同样,该模型易于掌握且非常可预测
Martin Geisler

1
马丁·盖斯勒(Martin Geisler)我认为,Mercurial中的标签规则非常容易掌握,因为它使用版本控制文件进行传输,并在特殊规则上加上一层以使标签不受版本控制,这是必需的。
JakubNarębski09年

5

我在目前的工作中使用Git已有一年多,在此之前,我在上一份工作中使用Mercurial已有一年多。我将从用户的角度进行评估。

首先,两者都是分布式版本控制系统。分布式版本控制系统需要改变传统版本控制系统的思维方式,但是一旦理解了它们,实际上可以在许多方面更好地工作。因此,我认为Git和Mercurial都比Subversion,Perforce等要好得多。分布式版本控制系统和传统版本控制系统之间的差异远大于Git和Mercurial之间的差异。

但是,Git和Mercurial之间也存在显着差异,这使它们各自更适合其自己的用例子集。

水银比较容易学习。在使用Mercurial几周后,我几乎不必去参考文档或说明。即使使用了一年,我仍然必须定期在Git上查阅我的笔记。Git非常复杂。

部分原因是Mercurial只是清洁工。您很少需要在Mercurial中手动进行分支。如果需要,Mercurial会自动为您创建一个匿名分支。水银的命名法更直观;您无需像使用Git一样担心“获取”和“拉动”之间的区别。水星的越野车少一些。在使用Git和Mercurial跨平台推送项目时,存在文件名区分大小写的问题,这些问题通常会引起问题。这是前一段时间在Mercurial中修复的,而我上次检查时并没有在Git中修复它们。您可以告诉Mercurial文件重命名;使用Git,如果它不能自动检测到重命名-根据我的经验,这是一个命中或错过的命题-则根本无法跟踪该重命名。

但是,Git额外复杂化的另一个原因是,它需要很多来支持其他功能和功能。是的,在Git中处理分支更加复杂-但是,另一方面,一旦有了分支,使用Mercurial中实际上不可能的分支做事就不太困难了。重新分支分支是其中之一:您可以移动分支,使分支的基础(而不是分支时的主干状态)成为主干的状态。当有很多人在同一个代码库上工作时,这可以大大简化版本历史记录,因为可以将每次推送到主干的操作显示为顺序的,而不是相互缠绕的。同样,将分支上的多个提交折叠为单个提交要容易得多,

最终,我认为在Mercurial和Git之间进行选择应该取决于版本控制项目的大小,以同时处理项目的人数来衡量。例如,如果您有十几个或更多的团队在一个单一的Web应用程序上工作,那么Git功能更强大的分支管理工具将使其更适合您的项目。另一方面,如果您的团队正在开发一个异构的分布式系统,并且一次只有一个或两个开发人员在任何一个组件上工作,则对每个组件项目使用Mercurial存储库将使开发工作更顺利,花费更少。存储库管理开销。

底线:如果您有一支庞大的团队来开发单个大型应用程序,请使用Git;如果您的单个应用程序很小,并且任何数量的规模都来自此类应用程序,而不是此类应用程序的大小,请使用Mercurial。


4

与DVCS本身完全不相关的一个区别是:

Git似乎在C开发人员中很受欢迎。Git是Linux内核的事实上的存储库,这可能是它在C开发人员中如此受欢迎的原因。对于那些只在Linux / Unix世界中工作的人来说尤其如此。

Java开发人员似乎更喜欢Mercurial,而不是Git。可能有两个原因:一是在Mercurial上托管了许多非常大的Java项目,包括JDK本身。另一个问题是,Mercurial的结构和简洁的文档吸引了来自Java阵营的人们,而这些人发现Git的wrt命令命名不一致并且缺乏文档。我并不是说这是真的,我是说人们已经习惯了通常的栖息地,然后他们倾向于从中选择DVCS。

我想,Python开发人员几乎完全偏爱Mercurial。除了Mercurial是基于Python的事实之外,实际上没有其他理由。(我也使用Mercurial,但我真的不明白为什么人们会对DVCS的实现语言大惊小怪。基于Python,那么我就不会知道)。

我认为您不能说一种DVCS比另一种更适合一种语言,因此您不应从中进行选择。但实际上,人们会(部分地)根据他们作为社区一部分接触到的DVCS进行选择。

(不,我没有使用统计信息来备份我的声明,这都是基于我自己的主观性)

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.