一段时间以来,我一直在为我的个人项目使用颠覆。
我越来越多地听到关于 Git 和 Mercurial 以及一般 DVCS 的好消息。
我想试一试整个 DVCS,但我对这两种选择都不太熟悉。
Mercurial 和 Git 之间有哪些区别?
注意:我不是想找出哪个是“最好的”,甚至不是我应该从哪个开始。我主要寻找它们相似和不同的关键领域,因为我很想知道它们在实现和理念方面有何不同。
免责声明:我使用 Git,在 git mailing list 上关注 Git 开发,甚至为 Git 贡献一点(主要是 gitweb)。我从文档中了解 Mercurial,从 FreeNode 上#revctrl IRC 频道的讨论中了解一些。
感谢#mercurial IRC 频道上所有为此文章提供有关 Mercurial 帮助的人
概括
在这里有一些表格语法会很好,比如 PHPMarkdown / MultiMarkdown / Markdown 的 Maruku 扩展
存储库结构:Mercurial 不允许章鱼合并(有两个以上的父级),也不允许标记非提交对象。
标签:Mercurial 使用版本化的 .hgtags 文件,并为每个存储库标签提供特殊规则,并且还支持 .hg/localtags 中的本地标签;在 Git 中,标签是位于 refs/tags/ 命名空间中的 refs,默认情况下在获取时会自动跟踪并需要显式推送。
分支:在 Mercurial 中,基本工作流程基于匿名头部; Git 使用轻量级命名分支,并具有特殊类型的分支(远程跟踪分支),这些分支遵循远程存储库中的分支。
修订命名和范围:Mercurial 提供版本号,本地到存储库,并基于此本地编号的相对修订(从尖端计数,即当前分支)和修订范围; Git 提供了一种相对于分支提示来引用修订的方法,并且修订范围是拓扑的(基于修订图)
Mercurial 使用重命名跟踪,而 Git 使用重命名检测来处理文件重命名
网络:Mercurial 支持 SSH 和 HTTP “智能”协议,以及静态 HTTP 协议;现代 Git 支持 SSH、HTTP 和 GIT“智能”协议,以及 HTTP(S)“哑”协议。两者都支持用于离线传输的捆绑文件。
Mercurial 使用扩展(插件)和已建立的 API; Git 具有可编写脚本和既定格式。
Mercurial 与 Git 有一些不同之处,但还有其他一些东西使它们相似。这两个项目都互相借鉴了想法。例如,Mercurial 中的 hg bisect
命令(以前的 bisect extension)受 Git 中的 git bisect
命令的启发,而 git bundle
的想法则受 hg bundle
的启发。
存储库结构,存储修订
在Git中,其对象数据库中有四种类型的对象:包含文件内容的blob对象,存储目录结构的分层树对象,包括文件名和文件权限的相关部分(文件的可执行权限,作为符号链接) ,包含作者信息的提交对象,指向由提交表示的版本库状态快照的指针(通过项目顶层目录的树对象)和对零个或多个父提交的引用,以及引用其他对象的标记对象,并且可以使用 PGP / GPG 进行签名。
Git 使用两种存储对象的方式:松散格式,其中每个对象存储在单独的文件中(这些文件只写入一次,从不修改),以及打包格式,其中许多对象以增量压缩方式存储在单个文件中。操作的原子性由以下事实提供,即在写入对象后写入对新对象的引用(原子地,使用 create + rename 技巧)。
Git 存储库需要使用 git gc
进行定期维护(以减少磁盘空间并提高性能),尽管现在 Git 会自动执行此操作。 (此方法提供了更好的存储库压缩。)
Mercurial(据我了解)将文件的历史记录存储在文件日志中(我认为,连同额外的元数据,如重命名跟踪和一些帮助信息);它使用称为 manifest 的平面结构来存储目录结构,并使用称为 changelog 的结构来存储有关变更集(修订)的信息,包括提交消息和零、一个或两个父级。
Mercurial 使用事务日志来提供操作的原子性,并依靠截断文件在操作失败或中断后进行清理。 Revlog 只能追加。
对比 Git 和 Mercurial 中的存储库结构,可以看出 Git 更像对象数据库(或内容寻址文件系统),而 Mercurial 更像传统的固定字段关系数据库。
区别:在 Git 中,树对象形成层次结构;在 Mercurial 清单文件中是扁平结构。在 Git blob 对象中存储文件内容的一个版本;在 Mercurial 文件日志中存储单个文件的整个历史记录(如果我们在这里不考虑重命名的任何复杂性)。这意味着在不同的操作领域,Git 会比 Mercurial 快,所有其他事情都被认为是相同的(例如合并,或显示项目历史),以及 Mercurial 会比 Git 快的领域(例如应用补丁或显示单个文件的历史记录)。这个问题对最终用户来说可能并不重要。
由于 Mercurial 的 changelog 结构的固定记录结构,Mercurial 中的提交最多只能有两个父级; Git 中的提交可以有两个以上的父级(所谓的“章鱼合并”)。虽然您可以(理论上)通过一系列双父合并替换章鱼合并,但这可能会在 Mercurial 和 Git 存储库之间转换时导致复杂化。
据我所知,Mercurial 没有来自 Git 的 注释标签(标签对象)。注释标签的一个特例是签名标签(带有PGP/GPG签名); Mercurial 中的等效项可以使用 GpgExtension 完成,该扩展与 Mercurial 一起分发。您不能像在 Git 中那样在 Mercurial 中标记未提交对象,但这并不是很重要,我认为(一些 git 存储库使用标记的 blob 分发公共 PGP 密钥以用于验证签名标签)。
参考:分支和标签
在 Git 中,引用(分支、远程跟踪分支和标签)位于提交的 DAG 之外(它们应该如此)。 refs/heads/
命名空间(本地分支)中的引用指向提交,通常由“git commit”更新;他们指向分支的尖端(头部),这就是为什么这样的名字。 refs/remotes/<remotename>/
命名空间(远程跟踪分支)中的引用指向提交,遵循远程存储库 <remotename>
中的分支,并由“git fetch”或等效项进行更新。 refs/tags/
命名空间 (tags) 中的引用通常指向提交(轻量级标签)或标签对象(带注释和签名的标签),并不意味着更改。
标签
在 Mercurial 中,您可以使用 tag 为修订提供持久名称;标签的存储方式与忽略模式类似。这意味着全局可见的标签存储在您的存储库中受修订控制的 .hgtags
文件中。这有两个后果:首先,Mercurial 必须为此文件使用特殊规则来获取所有标签的当前列表并更新此类文件(例如,它读取文件的最近提交的修订版本,而不是当前签出的版本);其次,您必须提交对此文件的更改,以使其他用户/其他存储库可以看到新标签(据我所知)。
Mercurial 还支持存储在 hg/localtags
中的 本地标签,这些标签对其他人不可见(当然也不能转让)
在 Git 中,标签是对存储在 refs/tags/
命名空间中的其他对象(通常是标签对象,又指向提交)的固定(恒定)命名引用。默认情况下,当获取或推送一组修订时,git 会自动获取或推送指向正在获取或推送的修订的标签。不过,您可以在一定程度上控制获取或推送哪些标签。
Git 处理轻量级标签(直接指向提交)和带注释的标签(指向标签对象,其中包含标签消息,其中可选地包括 PGP 签名,而后者又指向提交)略有不同,例如默认情况下它在描述时只考虑带注释的标签使用“git describe”提交。
Git 在 Mercurial 中没有严格等效的本地标签。尽管如此,git 最佳实践建议设置单独的公共裸存储库,您将准备好的更改推送到其中,其他人从中克隆和获取。这意味着您不推送的标签(和分支)对于您的存储库是私有的。另一方面,您也可以使用 heads
、remotes
或 tags
以外的命名空间,例如 local-tags
用于本地标记。
个人意见:在我看来,标签应该位于修订图之外,因为它们在修订图之外(它们是指向修订图的指针)。标签应该是非版本化的,但可以转移。 Mercurial 选择使用类似于忽略文件的机制,这意味着它要么必须特别对待 .hgtags
(文件 in-tree 是可转移的,但通常它是版本化的),或者只有本地的标签({2 } 是非版本化的,但不可转让)。
分支机构
在 Git 中,本地分支(分支提示或分支头)是对提交的命名引用,可以在其中增长新提交。分支也可以表示活跃的开发线,即从分支尖端可到达的所有提交。本地分支位于 refs/heads/
命名空间中,因此例如“master”分支的完全限定名称是“refs/heads/master”。
Git 中的当前分支(意思是签出的分支,以及新提交的分支)是 HEAD ref 引用的分支。可以让 HEAD 直接指向提交,而不是符号引用;这种在匿名未命名分支上的情况称为分离 HEAD(“git 分支”表明您在“(无分支)”上)。
在 Mercurial 中有匿名分支(分支头),并且可以使用书签(通过 bookmark extension)。这样的书签分支纯粹是本地的,并且这些名称(直到版本 1.6)不能使用 Mercurial 转移。您可以使用 rsync 或 scp 将 .hg/bookmarks
文件复制到远程存储库。您还可以使用 hg id -r <bookmark> <url>
获取当前书签提示的修订 ID。
从 1.6 开始,书签可以被推/拉。 BookmarksExtension 页面有一个关于 Working With Remote Repositories 的部分。 Mercurial 中的书签名称是全局,而 Git 中“远程”的定义也描述了从远程存储库中的名称到名称的 分支名称的映射本地远程跟踪分支机构;例如 refs/heads/*:refs/remotes/origin/*
映射意味着可以在 'origin/master' 远程跟踪分支 ('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> <分支>"推送给定的单个分支,您可以使用“git push remote> HEAD”来推送当前分支。
以上所有假设 Git 未配置通过 remote.<remotename>.push
配置变量推送哪些分支。
抓取中的分支
注意:这里我使用 Git 术语,其中“获取”表示从远程存储库下载更改将这些更改与本地工作集成。这就是“git fetch
”和“hg pull
”的作用。
如果我理解正确,默认情况下 Mercurial 会从远程存储库中获取 所有头,但您可以指定要通过“hg pull --rev <rev> <url>
”或“hg pull <url>#<rev>
”获取的分支以获取 单个分支< /强>。您可以指定 <rev>使用修订标识符、“命名分支”名称(嵌入在更改日志中的分支)或书签名称。但是书签名称(至少目前)不会被转移。您获得的所有“命名分支”修订都属于被转移。 “hg pull”将它获取的分支的提示存储为匿名的、未命名的头部。
默认情况下在 Git 中(对于“git clone”创建的“origin”远程,以及使用“git remote add”创建的远程)“git fetch
”(或“git fetch <remote>
”)获取所有分支 从远程存储库(来自 refs/heads/
命名空间),并将它们存储在 refs/remotes/
命名空间中。这意味着例如远程“origin”中名为“master”(全名:“refs/heads/master”)的分支将被存储(保存)为“origin/master”远程跟踪分支 (全名:'refs/remotes/origin/master')。
您可以使用 git fetch <remote> <branch>
在 Git 中获取 单个分支 - Git 会将请求的分支存储在 FETCH_HEAD 中,这类似于 Mercurial 未命名的头。
这些只是强大的 refspec Git 语法的默认情况的示例:使用 refspecs,您可以指定和/或配置要获取的分支以及存储它们的位置。例如,默认的“获取所有分支”案例由 '+refs/heads/*:refs/remotes/origin/*' 通配符 refspec 表示,而“获取单个分支”是 'refs/heads/
个人观点:我个人认为 Mercurial 中的“命名分支”(分支名称嵌入变更集元数据)是带有全局命名空间的错误设计,尤其是对于 分布式 版本控制系统。例如,让我们假设 Alice 和 Bob 在他们的存储库中都有名为“for-joe”的“命名分支”,这些分支没有任何共同之处。然而,在 Joe 的存储库中,这两个分支将被视为一个分支。因此,您以某种方式提出了防止分支名称冲突的约定。这对 Git 来说不是问题,在 Joe 的存储库中,来自 Alice 的“for-joe”分支是“alice/for-joe”,而来自 Bob 的则是“bob/for-joe”。另请参阅 Mercurial wiki 上提出的 Separating branch name from branch identity 问题。
Mercurial 的“书签分支”目前缺乏核心分发机制。
差异:
正如 james woodyatt 和 Steve Losh 在他们的回答中所说,这个领域是 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 仅提供来自 'tip'(当前分支)的相对偏移量,这些偏移量是存储库本地的(至少没有 ParentrevspecExtension),而 Git 允许指定任何提示之后的任何提交。
最新版本在 Git 中命名为 HEAD,在 Mercurial 中命名为“tip”; Git 中没有空版本。 Mercurial 和 Git 都可以有多个 root(可以有多个无父提交;这通常是以前单独的项目加入的结果)。
另请参阅: Many different kinds of revision specifiers Elijah 博客(newren 的)上的文章。
个人观点:我认为修订号被高估了(至少对于分布式开发和/或非线性/分支历史而言)。首先,对于分布式版本控制系统,它们必须要么位于存储库本地,要么需要以特殊方式将某个存储库视为中央编号机构。其次,历史较长的大型项目可以有 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 使用重命名跟踪 来处理文件重命名。这意味着文件被重命名的信息会在提交时保存;在 Mercurial 中,此信息以“增强的差异”形式保存在 filelog(文件 revlog)元数据中。这样做的结果是您必须使用 hg rename
/ hg mv
... 或者您需要记住运行 hg addremove
来进行基于相似性的重命名检测。
Git 在版本控制系统中是独一无二的,因为它使用重命名检测来处理文件重命名。这意味着文件被重命名的事实在需要时被检测到:在进行合并时,或在显示差异时(如果请求/配置)。这样做的好处是可以改进重命名检测算法,并且不会在提交时冻结。
在显示单个文件的历史记录时,Git 和 Mercurial 都需要使用 --follow
选项来跟随重命名。在 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。这些协议中的交换包括客户端和服务器协商它们共有的对象,然后生成和发送一个包文件。现代 Git 包括对“智能”HTTP 协议的支持。
“哑”协议,包括 HTTP 和 FTP(仅用于获取)和 HTTPS(用于通过 WebDAV 推送),不需要在服务器上安装 git,但它们确实需要存储库包含由 git update-server-info 生成的额外信息(通常从钩子上运行)。交换包括客户端遍历提交链并根据需要下载松散的对象和包文件。缺点是它的下载量超过了严格要求(例如,在只有单个包文件的极端情况下,即使只获取几个修订版,它也会被整个下载),并且它可能需要许多连接才能完成。
扩展:可编写脚本与扩展(插件)
Mercurial 是用 Python 实现的,一些核心代码是用 C 编写的以提高性能。它提供了用于编写扩展(插件)的 API,作为添加额外功能的一种方式。一些功能,如“书签分支”或签名修订,在随 Mercurial 分发的扩展中提供,需要打开它。
Git 在 C、Perl 和 shell 脚本中实现。 Git 提供了许多适合在脚本中使用的低级命令(管道)。引入新功能的通常方法是将其编写为 Perl 或 shell 脚本,当用户界面稳定时,用 C 重写它以提高性能、可移植性,并在 shell 脚本的情况下避免极端情况(此过程称为内置)。
Git 依赖并围绕 [repository] 格式和 [network] 协议构建。除了语言绑定之外,还有其他语言(部分或完全)对 Git 的重新实现(其中一些是部分重新实现,部分是 git 命令的包装):JGit(Java,由 EGit、Eclipse Git 插件使用)、Grit(Ruby) , 德威 (Python), git# (C#)。
TL;博士
我认为您可以通过观看这两个视频来了解这些系统的相似之处或不同之处:
Linus Torvalds 谈 Git (http://www.youtube.com/watch?v=4XpnKHJAok8)
Bryan O'Sullivan 谈 Mercurial (http://www.youtube.com/watch?v=JExtkqzEoHY)
它们在设计上非常相似,但在实现上却非常不同。
我使用 Mercurial。据我了解 Git,git 的一个主要不同之处在于它跟踪文件的内容而不是文件本身。 Linus 说,如果您将一个函数从一个文件移动到另一个文件,Git 会告诉您该单个函数在整个移动过程中的历史记录。
他们还说 git 比 HTTP 慢,但它有自己的网络协议和服务器。
Git 作为 SVN 胖客户端比 Mercurial 更有效。您可以对 SVN 服务器进行拉取和推送。 Mercurial 中仍在开发此功能
Mercurial 和 Git 都有非常好的网络托管解决方案(BitBucket 和 GitHub),但 Google Code 仅支持 Mercurial。顺便说一句,他们对 Mercurial 和 Git 进行了非常详细的比较,以确定支持哪一个 (http://code.google.com/p/support/wiki/DVCSAnalysis)。它有很多很好的信息。
我经常使用两者。主要的功能差异在于 Git 和 Mercurial 名称在存储库中的分支方式。使用 Mercurial,分支名称与它们的变更集一起被克隆和拉取。当您将更改添加到 Mercurial 中的新分支并推送到另一个存储库时,会同时推送分支名称。因此,Mercurial 中的分支名称或多或少是全局的,您必须使用 Bookmark 扩展来获得仅限本地的轻量级名称(如果需要的话;Mercurial 默认使用匿名轻量级代码行,在其术语中是称为“头”)。在 Git 中,分支名称及其到远程分支的单射映射存储在本地,您必须明确地管理它们,这意味着知道如何做到这一点。这几乎就是 Git 比 Mercurial 更难学习和使用而闻名的地方。
正如其他人会在这里指出的那样,有很多很多细微的差异。分支是最大的区别。
Mercurial 几乎完全用 python 编写。 Git 的核心是用 C 编写的(应该比 Mercurial 的更快),工具是用 sh、perl、tcl 编写的,并使用标准的 GNU 实用程序。因此,它需要将所有这些实用程序和解释器带到不包含它们的系统(例如 Windows)。
两者都支持 SVN,尽管 AFAIK svn 对 Windows 上的 git 的支持已被破坏(可能我只是不走运/跛脚,谁知道呢)。还有一些扩展允许在 git 和 Mercurial 之间进行互操作。
Mercurial 有很好的 Visual Studio integration。上次我检查时,plugin for Git 正在工作,但速度极慢。
它们的基本命令集非常相似(init、clone、add、status、commit、push、pull 等)。因此,基本工作流程将是相同的。此外,两者都有类似 TortoiseSVN 的客户端。
Mercurial 的扩展可以用 python 编写(不足为奇!),而对于 git,它们可以用任何可执行形式编写(可执行二进制文件、shell 脚本等)。有些扩展非常强大,例如 git bisect
。
如果您需要良好的 Windows 支持,您可能更喜欢 Mercurial。 TortoiseHg(Windows 资源管理器插件)设法为一个相当复杂的工具提供了一个简单易用的图形界面。正如此处所述,您还将拥有一个 Visual Studio plugin。但是,我上次尝试时,SVN 界面在 Windows 上运行得不是很好。
如果你不介意命令行界面,我会推荐 Git。不是出于技术原因,而是出于战略原因。 git的采用率要高得多。看看有多少著名的开源项目正在从 cvs/svn 切换到 Mercurial,有多少正在切换到 Git。与 Mercurial 托管相比,您可以通过 git 支持找到多少代码/项目托管服务提供商。
在阅读完 Mercurial 更容易(我仍然相信它,毕竟互联网社区是这样认为的)之后,当我开始使用 Git 和 Mercurial 时,我觉得 Git 对我来说相对更容易适应(我开始使用 Mercurial 和 TortoiseHg)从命令行工作时,主要是因为 git 命令根据我的说法适当命名并且数量较少。 Mercurial 对每个执行不同工作的命令都有不同的命名,而根据情况,Git 命令可以是多用途的(例如,checkout
)。虽然当时 Git 更难,但现在差别不大。 YMMV .. 使用像 TortoiseHg 这样的良好 GUI 客户端,确实使用 Mercurial 更容易,而且我不必记住稍微令人困惑的命令。我不会详细说明同一操作的每个命令是如何变化的,但这里有两个综合列表:1 from Mercurial's own site 和 2nd from 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 有子存储库,但这不完全是 Git 子模块。
围绕这两者的生态系统也会影响一个人的选择:Git 必须更受欢迎(但这很微不足道),Git 有 GitHub,而 Mercurial 有 BitBucket,Mercurial 有 TortoiseHg,我还没有看到对 Git 有同样好的效果.
每个都有其优点和缺点,其中任何一个你都不会失去。
不久前查看 Scott Chacon's post。
我认为 git 以“更复杂”而闻名,尽管根据我的经验,它并没有比它需要的更复杂。 IMO,git模型更容易理解(标签包含提交(以及指向零个或多个父提交的指针)包含树包含blob和其他树......完成)。
git 并不比 mercurial 更令人困惑,这不仅仅是我的经验。我建议再次阅读有关此问题的 this blog post from Scott Chacon。
.hgtags
中看不到 1.0 标记这一事实常常使用户感到困惑。但是,您无需查看 .hgtags
内部,您会发现 hg tags
仍然列出所有标记。此外,这种行为是在版本控制文件中存储标签的简单结果——同样,该模型很容易掌握并且非常可预测。
在我现在的工作中,我使用 Git 一年多一点,在此之前,在我之前的工作中使用 Mercurial 一年多一点。我将从用户的角度进行评估。
首先,两者都是分布式版本控制系统。分布式版本控制系统需要改变传统版本控制系统的思维方式,但一旦理解它们,实际上在许多方面工作得更好。出于这个原因,我认为 Git 和 Mercurial 都比 Subversion、Perforce 等优越得多。分布式版本控制系统与传统版本控制系统之间的差异远大于 Git 和 Mercurial 之间的差异。
但是,Git 和 Mercurial 之间也存在显着差异,这使得它们更适合自己的用例子集。
Mercurial 更容易学习。在使用 Mercurial 几周后,我很少需要参考文档或注释。即使在使用了一年之后,我仍然需要定期使用 Git 参考我的笔记。 Git 要复杂得多。
这部分是因为 Mercurial 更干净。您很少需要在 Mercurial 中手动分支; Mercurial 会在您需要时自动为您创建一个匿名分支。 Mercurial命名法更直观;您不必像使用 Git 那样担心“获取”和“拉取”之间的区别。 Mercurial的越野车少一些。在使用 Git 和 Mercurial 跨平台推送项目时,存在文件名区分大小写问题;这在一段时间前已在 Mercurial 中修复,而我上次检查时尚未在 Git 中修复。您可以告诉 Mercurial 文件重命名;使用 Git,如果它没有自动检测到重命名 - 根据我的经验,这是一个非常成功或错过的提议 - 根本无法跟踪重命名。
然而,Git 额外复杂性的另一个原因是需要它来支持额外的功能和功能。是的,在 Git 中处理分支更复杂 - 但另一方面,一旦你有了分支,用 Mercurial 中几乎不可能的那些分支做一些事情并不太难。重新设置分支是其中之一:您可以移动分支,使其基础,而不是分支时的主干状态,现在是主干的状态;当有很多人在同一个代码库上工作时,这大大简化了版本历史,因为每次推送到主干都可以看起来是连续的,而不是相互交织的。同样,将分支上的多个提交合并为一个提交要容易得多,这再次有助于保持版本控制历史的整洁:理想情况下,一个功能的所有工作都可以在主干中显示为单个提交,替换所有次要提交开发人员在开发该功能时可能进行的提交和子分支。
最终,我认为 Mercurial 和 Git 之间的选择应该取决于您的版本控制项目有多大,以同时工作的人数来衡量。例如,如果您有十几个人或更多人在开发一个单一的 Web 应用程序,那么 Git 更强大的分支管理工具将使其更适合您的项目。另一方面,如果您的团队正在开发一个异构分布式系统,任何时候只有一两个开发人员在任何一个组件上工作,那么为每个组件项目使用 Mercurial 存储库将允许开发以更少的资源更顺利地进行。存储库管理开销。
底线:如果你有一个大团队正在开发一个庞大的应用程序,请使用 Git;如果您的个人应用程序很小,并且任何规模都来自此类应用程序的数量而不是大小,请使用 Mercurial。
一个与 DVCS 本身完全无关的差异:
Git 似乎很受 C 开发人员的欢迎。 Git 是 Linux 内核事实上的存储库,这可能是它在 C 开发人员中如此受欢迎的原因。对于那些只在 Linux/Unix 世界中工作的人来说尤其如此。
Java 开发人员似乎更喜欢 Mercurial 而不是 Git。这可能有两个原因:一个是许多非常大的 Java 项目都托管在 Mercurial 上,包括 JDK 本身。另一个原因是 Mercurial 的结构和干净的文档吸引了来自 Java 阵营的人,而这些人发现 Git 不一致的 wrt 命令命名和缺乏文档。我并不是说这实际上是真的,我是说人们已经习惯了他们通常的栖息地,然后他们倾向于从中选择 DVCS。
我认为 Python 开发人员几乎完全偏爱 Mercurial。除了 Mercurial 基于 Python 的事实之外,实际上没有任何合理的理由。 (我也使用 Mercurial,我真的不明白为什么人们对 DVCS 的实现语言大惊小怪。我一个字都不懂 Python,如果不是因为它在某个地方列出了它是基于 Python 的,那我就不知道了)。
我认为你不能说一种 DVCS 比另一种更适合一种语言,所以你不应该从中选择。但在现实中,人们(部分)根据他们作为社区的一部分最容易接触到的 DVCS 来选择。
(不,我没有使用统计数据来支持我上面的说法......这完全基于我自己的主观性)