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

我听到越来越多关于Git、Mercurial和DVCS的好消息。

我想尝试一下DVCS,但我对这两种选择都不太熟悉。

Mercurial和Git之间有什么区别?

注意:我并不是要找出哪一个是“最好的”,或者我应该从哪一个开始。我主要寻找它们相似和不同的关键领域,因为我想知道它们在实现和理念方面有何不同。


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

Linus Torvalds谈Git (http://www.youtube.com/watch?v=4XpnKHJAok8) 布莱恩·奥沙利文谈Mercurial (http://www.youtube.com/watch?v=JExtkqzEoHY)

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

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

他们还说git在HTTP上更慢,但它有自己的网络协议和服务器。

Git作为SVN的厚客户端比Mercurial更好。可以对SVN服务器进行拉推操作。这个功能在Mercurial中仍处于开发阶段

Mercurial和Git都有非常好的网络托管解决方案(BitBucket和GitHub),但谷歌代码只支持Mercurial。顺便说一下,他们对Mercurial和Git进行了非常详细的比较,以决定支持哪一个(http://code.google.com/p/support/wiki/DVCSAnalysis)。它有很多好的信息。

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

两者都支持SVN,尽管AFAIK SVN在Windows上对git的支持是坏的(可能是我不走运/蹩脚,谁知道呢)。还有一些扩展允许git和Mercurial之间的互操作。

Mercurial有很好的Visual Studio集成。上次我检查的时候,Git的插件正在工作,但是非常慢。

它们的基本命令集非常相似(init, clone, add, status, commit, push, pull等)。所以,基本的工作流程是一样的。另外,两者都有类似tortoisesvn的客户端。

Mercurial的扩展可以用python编写(这并不奇怪!),而git的扩展可以用任何可执行形式编写(可执行二进制文件、shell脚本等)。有些扩展功能非常强大,比如git bisect。

I use both quite regularly. The major functional difference is in the way Git and Mercurial name branches within repositories. With Mercurial, branch names are cloned and pulled along with their changesets. When you add changes to a new branch in Mercurial and push to another repository, the branch name is pushed at the same time. So, branch names are more-or-less global in Mercurial, and you have to use the Bookmark extension to have local-only lightweight names (if you want them; Mercurial, by default, uses anonymous lightweight codelines, which in its terminology are called "heads"). In Git, branch names and their injective mapping to remote branches are stored locally and you must manage them explicitly, which means knowing how to do that. This is pretty much where Git gets its reputation for being harder to learn and use than Mercurial.

正如其他人在这里注意到的,有很多很多微小的差异。分支是一个很大的微分器。

看看Scott Chacon不久前的帖子。

我认为git以“更复杂”而闻名,尽管在我的经验中,它并没有比它需要的更复杂。在我看来,git模型更容易理解(标签包含提交(以及指向零个或多个父提交的指针)包含树,包含blob和其他树…完成)。

git并不比mercurial更令人困惑,这不仅仅是我的经验。我建议你再读一遍Scott Chacon关于这个问题的博文。

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

感谢所有在#mercurial IRC频道为这篇文章提供帮助的人



总结

在这里,有一些表的语法会很好,就像在PHPMarkdown / MultiMarkdown / Maruku Markdown的扩展

Repository structure: Mercurial doesn't allow octopus merges (with more than two parents), nor tagging non-commit objects. Tags: Mercurial uses versioned .hgtags file with special rules for per-repository tags, and has also support for local tags in .hg/localtags; in Git tags are refs residing in refs/tags/ namespace, and by default are autofollowed on fetching and require explicit pushing. Branches: In Mercurial basic workflow is based on anonymous heads; Git uses lightweight named branches, and has special kind of branches (remote-tracking branches) that follow branches in remote repository. Revision naming and ranges: Mercurial provides revision numbers, local to repository, and bases relative revisions (counting from tip, i.e. current branch) and revision ranges on this local numbering; Git provides a way to refer to revision relative to branch tip, and revision ranges are topological (based on graph of revisions) Mercurial uses rename tracking, while Git uses rename detection to deal with file renames Network: Mercurial supports SSH and HTTP "smart" protocols, and static HTTP protocol; modern Git supports SSH, HTTP and GIT "smart" protocols, and HTTP(S) "dumb" protocol. Both have support for bundles files for off-line transport. Mercurial uses extensions (plugins) and established API; Git has scriptability and established formats.


Mercurial与Git有一些不同之处,但也有一些相似之处。这两个项目都借鉴了彼此的想法。例如Mercurial中的hg bisect命令(以前的bisect扩展)是受到git中的git bisect命令的启发,而git bundle的想法则是受到hg bundle的启发。

存储库结构,存储修订

In Git there are four types of objects in its object database: blob objects which contain contents of a file, hierarchical tree objects which store directory structure, including file names and relevant parts of file permissions (executable permission for files, being a symbolic link), commit object which contain authorship info, pointer to snapshot of state of repository at revision represented by a commit (via a tree object of top directory of project) and references to zero or more parent commits, and tag objects which reference other objects and can be signed using PGP / GPG.

Git使用两种方式存储对象:松散格式,其中每个对象存储在一个单独的文件中(这些文件只写入一次,从不修改),以及打包格式,其中许多对象存储在一个文件中。操作的原子性是由这样一个事实提供的,即对新对象的引用是在写入对象后写入的(原子地,使用create + rename技巧)。

Git存储库需要使用Git gc进行定期维护(以减少磁盘空间并提高性能),尽管现在Git会自动完成这些工作。(此方法提供了更好的存储库压缩。)

Mercurial(据我所知)将文件的历史记录存储在文件日志中(我认为还包括额外的元数据,如重命名跟踪和一些辅助信息);它使用扁平结构manifest来存储目录结构,使用名为changelog的结构来存储关于变更集(修订)的信息,包括提交消息和0、1或2个父级。

Mercurial使用事务日志来提供操作的原子性,并依赖于截断文件来在失败或中断操作后进行清理。revlog只能追加。

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

Differences: In Git the tree objects form a hierarchical structure; in Mercurial manifest file is flat structure. In Git blob object store one version of a contents of a file; in Mercurial filelog stores whole history of a single file (if we do not take into account here any complications with renames). This means that there are different areas of operations where Git would be faster than Mercurial, all other things considered equal (like merges, or showing history of a project), and areas where Mercurial would be faster than Git (like applying patches, or showing history of a single file). This issue might be not important for end user.

由于Mercurial的更新日志结构的固定记录结构,在Mercurial中提交最多只能有两个父节点;Git中的提交可以有两个以上的父文件(所谓的“章鱼合并”)。虽然(理论上)可以用一系列双亲合并来取代octopus合并,但在Mercurial和Git存储库之间进行转换时,这可能会导致复杂情况。

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

引用:分支和标记

In Git references (branches, remote-tracking branches and tags) reside outside DAG of commits (as they should). References in refs/heads/ namespace (local branches) point to commits, and are usually updated by "git commit"; they point to the tip (head) of branch, that's why such name. References in refs/remotes/<remotename>/ namespace (remote-tracking branches) point to commit, follow branches in remote repository <remotename>, and are updated by "git fetch" or equivalent. References in refs/tags/ namespace (tags) point usually to commits (lightweight tags) or tag objects (annotated and signed tags), and are not meant to change.

Tags

In Mercurial you can give persistent name to revision using tag; tags are stored similarly to the ignore patterns. It means that globally visible tags are stored in revision-controlled .hgtags file in your repository. That has two consequences: first, Mercurial has to use special rules for this file to get current list of all tags and to update such file (e.g. it reads the most recently committed revision of the file, not currently checked out version); second, you have to commit changes to this file to have new tag visible to other users / other repositories (as far as I understand it).

Mercurial还支持本地标记,存储在hg/localtags中,其他人不可见(当然也不能转移)。

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

Git对待轻量级标记(直接指向提交)和带注释的标记(指向标记对象,其中包含标记消息,可选地包含PGP签名,然后指向提交)略有不同,例如默认情况下,当使用“Git describe”描述提交时,它只考虑带注释的标记。

Git在Mercurial中没有严格的本地标记对等物。然而,git最佳实践建议设置单独的公共裸库,您可以将准备好的更改推入其中,其他人可以从中克隆和获取。这意味着您不推送的标记(和分支)对于存储库来说是私有的。另一方面,您也可以使用除头、远程或标记以外的名称空间,例如本地标记的local-tags。

个人观点:在我看来,标签应该位于修订图之外,因为它们在修订图之外(它们是指向修订图的指针)。标签应该是无版本控制的,但是可以转移。Mercurial选择使用一种类似于忽略文件的机制,这意味着它要么必须特别对待.hgtags(文件树是可传输的,但普通的是有版本控制的),要么只有本地标记(。Hg /localtags是无版本的,但不可转让)。

分支机构

在Git中,本地分支(分支尖端或分支头)是对提交的命名引用,在这里可以生成新的提交。分支也可以表示活跃的开发线,即从分支尖端可到达的所有提交。本地分支位于refs/heads/ namespace中,例如,'master'分支的全限定名是'refs/heads/master'。

Git中的当前分支(即检出的分支,以及新提交的分支)是由HEAD引用引用的分支。可以让HEAD直接指向提交,而不是符号引用;这种位于匿名分支上的情况称为分离HEAD(“git分支”显示您位于“(无分支)”上)。

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

Since 1.6 bookmarks can be pushed/pulled. The BookmarksExtension page has a section on Working With Remote Repositories. There is a difference in that in Mercurial bookmark names are global, while definition of 'remote' in Git describes also mapping of branch names from the names in remote repository to the names of local remote-tracking branches; for example refs/heads/*:refs/remotes/origin/* mapping means that one can find state of 'master' branch ('refs/heads/master') in the remote repository in the 'origin/master' remote-tracking branch ('refs/remotes/origin/master').

Mercurial也有所谓的命名分支,其中分支名称嵌入在提交中(在变更集中)。这样的名称是全局的(在获取时传输)。这些分支名称被永久地记录为变更集\u2019s元数据的一部分。使用现代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 <remote> HEAD"来推送当前分支。

以上都假设Git没有配置通过remote推送哪个分支。推送配置变量。

抓取中的分支

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

If I understand it correctly, by default Mercurial fetches all heads from remote repository, but you can specify branch to fetch via "hg pull --rev <rev> <url>" or "hg pull <url>#<rev>" to get single branch. You can specify <rev> using revision identifier, "named branch" name (branch embedded in changelog), or bookmark name. Bookmark name however (at least currently) doesn't get transferred. All "named branches" revisions you get belong to get transferred. "hg pull" stores tips of branches it fetched as anonymous, unnamed heads.

默认在Git中(对于“Git clone”创建的“origin”远程,以及使用“Git remote add”创建的远程)"git fetch"(或"git fetch <remote>")从远程存储库(from refs/heads/ namespace)获取所有分支,并将它们存储在refs/remotes/ namespace中。这意味着,例如,在远程“origin”中名为“master”(全名:'refs/heads/master')的分支将被存储(保存)为'origin/master'远程跟踪分支(全名:'refs/remotes/origin/master')。

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

Those are but examples of default cases of powerful refspec Git syntax: with refspecs you can specify and/or configure which branches one want to fetch, and where to store them. For example default "fetch all branches" case is represented by '+refs/heads/*:refs/remotes/origin/*' wildcard refspec, and "fetch single branch" is shorthand for 'refs/heads/<branch>:'. Refspecs are used to map names of branches (refs) in remote repository to local refs names. But you don't need to know (much) about refspecs to be able to work effectively with Git (thanks mainly to "git remote" command).

Personal opinion: I personally think that "named branches" (with branch names embedded in changeset metadata) in Mercurial are misguided design with its global namespace, especially for a distributed version control system. For example let's take case where both Alice and Bob have "named branch" named 'for-joe' in their repositories, branches which have nothing in common. In Joe's repository however those two branches would be mistreated as a single branch. So you have somehow come up with convention protecting against branch name clashes. This is not problem with Git, where in Joe's repository 'for-joe' branch from Alice would be 'alice/for-joe', and from Bob it would be 'bob/for-joe'. See also Separating branch name from branch identity issue raised on Mercurial wiki.

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

Differences: This area is one of the main differences between Mercurial and Git, as james woodyatt and Steve Losh said in their answers. Mercurial, by default, uses anonymous lightweight codelines, which in its terminology are called "heads". Git uses lightweight named branches, with injective mapping to map names of branches in remote repository to names of remote-tracking branches. Git "forces" you to name branches (well, with exception of single unnamed branch, situation called detached HEAD), but I think this works better with branch-heavy workflows such as topic branch workflow, meaning multiple branches in single repository paradigm.

命名的修正

在Git中有很多命名版本的方法(例如在Git rev-parse manpage中描述):

The full SHA1 object name (40-byte hexadecimal string), or a substring of such that is unique within the repository A symbolic ref name, e.g. 'master' (referring to 'master' branch), or 'v1.5.0' (referring to tag), or 'origin/next' (referring to remote-tracking branch) A suffix ^ to revision parameter means the first parent of a commit object, ^n means n-th parent of a merge commit. A suffix ~n to revision parameter means n-th ancestor of a commit in straight first-parent line. Those suffixes can be combined, to form revision specifier following path from a symbolic reference, e.g. 'pu~3^2~3' Output of "git describe", i.e. a closest tag, optionally followed by a dash and a number of commits, followed by a dash, a 'g', and an abbreviated object name, for example 'v1.6.5.1-75-g5bf8097'.

还有涉及reflog的修订说明符,这里没有提到。在Git中,每个对象,无论是提交、标记、树还是blob都有自己的SHA-1标识符;有一些特殊的语法,比如e.g.。'next:文档'或'next:README'指树(目录)或blob(文件内容)在指定的修订。

Mercurial也有许多命名变更集的方法(例如在hg manpage中描述):

A plain integer is treated as a revision number. One need to remember that revision numbers are local to given repository; in other repository they can be different. Negative integers are treated as sequential offsets from the tip, with -1 denoting the tip, -2 denoting the revision prior to the tip, and so forth. They are also local to repository. An unique revision identifier (40-digit hexadecimal string) or its unique prefix. A tag name (symbolic name associated with given revision), or a bookmark name (with extension: symbolic name associated with given head, local to repository), or a "named branch" (commit label; revision given by "named branch" is tip (childless commit) of all commits with given commit label, with largest revision number if there are more than one such tip) The reserved name "tip" is a special tag that always identifies the most recent revision. The reserved name "null" indicates the null revision. The reserved name "." indicates the working directory parent.

差异 从上面的列表可以看出,Mercurial提供了本地版本号,而Git没有。另一方面,Mercurial只提供从'tip'(当前分支)的相对偏移量,这是存储库的本地偏移量(至少没有ParentrevspecExtension),而Git允许从任何tip指定任何提交。

最新的版本在Git中被命名为HEAD,在Mercurial中被命名为“tip”;Git中没有空修订。Mercurial和Git都可以有多个根(可以有多个无父提交;这通常是以前独立的项目合并的结果)。

参见:以利亚博客(newren’s)上的许多不同类型的修订说明文章。

Personal opinion: I think that revision numbers are overrated (at least for distributed development and/or nonlinear / branchy history). First, for a distributed version control system they have to be either local to repository, or require treating some repository in a special way as a central numbering authority. Second, larger projects, with longer history, can have number of revisions in 5 digits range so they are offer only slight advantage over shortened to 6-7 character revision identifiers, and imply strict ordering while revisions are only partially ordered (I mean here that revisions n and n+1 doesn't need to be parent and child).

修改范围

In Git revision ranges are topological. Commonly seen A..B syntax, which for linear history means revision range starting at A (but excluding A), and ending at B (i.e. range is open from below), is shorthand ("syntactic sugar") for ^A B, which for history traversing commands mean all commits reachable from B, excluding those reachable from A. This means that the behavior of A..B range is entirely predictable (and quite useful) even if A is not ancestor of B: A..B means then range of revisions from common ancestor of A and B (merge base) to revision B.

In Mercurial revision ranges are based on range of revision numbers. Range is specified using A:B syntax, and contrary to Git range acts as a closed interval. Also range B:A is the range A:B in reverse order, which is not the case in Git (but see below note on A...B syntax). But such simplicity comes with a price: revision range A:B makes sense only if A is ancestor of B or vice versa, i.e. with linear history; otherwise (I guess that) the range is unpredictable, and the result is local to repository (because revision numbers are local to repository).

Mercurial 1.6修复了这个问题,它有新的拓扑修正范围,其中'A..B'(或'A::B')被理解为既是X的后代,又是y的祖先的变更集的集合。我想,这相当于'——祖宗路径A..B'在Git里。

Git也有符号A…B表示修正的对称差分;它的意思是A B——而不是$(git merge-base A B),这意味着所有的提交都可以从A或B中访问,但不包括所有的提交都可以从它们中访问(从公共祖先中访问)。

重命名

Mercurial使用重命名跟踪来处理文件重命名。这意味着关于文件重命名的信息在提交时保存;在Mercurial中,这些信息以“enhanced diff”形式保存在filelog(文件revlog)元数据中。这样做的后果是你必须使用hg重命名/ hg mv…或者你需要记得运行hg adremove来进行基于相似性的重命名检测。

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

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

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

网络协议

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

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

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

"smart" protocols, which include access via SSH and via custom git:// protocol (by git-daemon), require having git installed on server. The exchange in those protocols consist of client and server negotiating about what objects they have in common, and then generating and sending a packfile. Modern Git includes support for "smart" HTTP protocol. "dumb" protocols, which include HTTP and FTP (only for fetching), and HTTPS (for pushing via WebDAV), do not require git installed on server, but they do require that repository contains extra information generated by git update-server-info (usually run from a hook). The exchange consist of client walking the commit chain and downloading loose objects and packfiles as needed. The downside is that it downloads more than strictly required (e.g. in corner case when there is only single packfile it would get downloaded whole even when fetching only a few revisions), and that it can require many connections to finish.

扩展:可脚本性vs扩展(插件)

Mercurial是用Python实现的,为了提高性能,一些核心代码是用C编写的。它提供了用于编写扩展(插件)的API,作为添加额外特性的一种方式。一些功能,如“书签分支”或签名修订,是在随Mercurial分发的扩展中提供的,需要打开它。

Git是用C、Perl和shell脚本实现的。Git提供了许多适合在脚本中使用的低级命令(管道)。引入新特性的通常方法是将其编写为Perl或shell脚本,当用户界面稳定时,为了性能、可移植性和避免shell脚本的极端情况(此过程称为内置化),用C重写它。

Git依赖并构建在[存储库]格式和[网络]协议之上。除了语言绑定,还有其他语言(部分或完全)对Git的重新实现(其中一些是部分重新实现,部分是对Git命令的包装):JGit (Java,由EGit使用,Eclipse Git Plugin), Grit (Ruby), Dulwich (Python), git# (c#)。


博士TL;

如果您需要良好的Windows支持,您可能更喜欢Mercurial。TortoiseHg (Windows资源管理器插件)设法为一个相当复杂的工具提供了一个简单易用的图形界面。在这里,你还会有一个Visual Studio插件。但是,上次我尝试时,SVN界面在Windows上工作得不太好。

如果您不介意使用命令行界面,我推荐使用Git。不是出于技术原因,而是出于战略原因。git的采用率高得多。看看有多少著名的开源项目正在从cvs/svn切换到Mercurial,又有多少项目正在切换到Git。看看与Mercurial托管相比,你能找到多少支持git的代码/项目托管提供商。

After reading all over that Mercurial is easier (which I still believe it is, after all the internet community is of the opinion), when I started working with Git and Mercurial I felt Git is relatively simpler for me to adapt to (I started off with Mercurial with TortoiseHg) when working from the command line, mainly because the git commands were named appropriately according to me and are fewer in number. Mercurial has different naming for each command that does a distinct job, while Git commands can be multipurpose according to situation (for eg, checkout). While Git was harder back then, now the difference is hardly substantial. YMMV.. With a good GUI client like TortoiseHg, true it was much easier to work with Mercurial and I did not have to remember the slightly confusing commands. I'm not going into detail how every command for the same action varied, but here are two comprehensive lists: 1 from Mercurial's own site and 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有subrepos,但那不是Git的子模块。

围绕这两者的生态系统也会影响一个人的选择:Git必须更受欢迎(但这无关紧要),Git有GitHub,而Mercurial有BitBucket, Mercurial有TortoiseHg,我还没有看到与Git一样好的同类产品。

每一种都有它的优点和缺点,用它们中的任何一种你都不会输。

与DVCSs本身完全无关的一个区别是:

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

Java developers seem to favor Mercurial over Git. There are possibly two reasons for that: One is that a number of very large Java projects are hosted on Mercurial, including the JDK itself. Another is that the structure and clean documentation of Mercurial appeals to people coming from the Java camp whereas such people find Git inconsistent wrt command naming and lacking in documentation. I'm not saying that is actually true, I'm saying people have got used to something from their usual habitat and then they tend to choose DVCS from that.

我认为Python开发人员几乎都喜欢Mercurial。除了Mercurial是基于Python的这一事实之外,实际上没有任何合理的原因。(我也使用Mercurial,我真的不明白为什么人们对DVCS的实现语言大惊小怪。我不懂Python的一个字,如果不是因为它在某个地方列出了它是基于Python的,那么我不会知道)。

我认为你不能说一个DVCS比另一个更适合一种语言,所以你不应该从中做出选择。但在现实中,人们选择(部分)是基于他们在社区中接触最多的DVCS。

(不,我没有使用统计数据来支持我上面的说法..这都是基于我自己的主观)

在我目前的工作中,我已经使用Git一年多一点,在此之前,我在上一份工作中使用Mercurial一年多一点。我将从用户的角度提供一个评估。

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

然而,Git和Mercurial之间也存在显著的差异,这使得它们都更适合于自己的用例子集。

Mercurial比较容易学习。在使用Mercurial几周后,我几乎不需要参考文档或笔记;即使在使用Git一年之后,我仍然需要定期查阅我的笔记。Git要复杂得多。

This is partly because Mercurial is just plain cleaner. You rarely have to branch manually in Mercurial; Mercurial will create an anonymous branch automatically for you if and when you need it. Mercurial nomenclature is more intuitive; you don't have to worry about the difference between "fetch" and "pull" as you do with Git. Mercurial is a bit less buggy. There are file name case sensitivity issues that used to cause problems when pushing projects across platforms with both Git and Mercurial; this were fixed in Mercurial some time ago while they hadn't been fixed in Git last I checked. You can tell Mercurial about file renames; with Git, if it doesn't detect the rename automatically - a very hit or miss proposition in my experience - the rename can't be tracked at all.

The other reason for Git's additional complication, however, is that much of it is needed to support additional features and power. Yes, it's more complicated to handle branching in Git - but on the other hand, once you have the branches, it's not too difficult to do things with those branches that are virtually impossible in Mercurial. Rebasing branches is one of these things: you can move your branch so that its base, instead of being the state of the trunk when you branched, is the state of the trunk now; this greatly simplifies version history when there are many people working on the same code base, since each of the pushes to trunk can be made to appear sequential, rather than intertwined. Similarly, it's much easier to collapse multiple commits on your branch into a single commit, which can again help in keeping the version control history clean: ideally, all the work on a feature can appear as a single commit in trunk, replacing all the minor commits and subbranches that the developer may have made while developing the feature.

Ultimately I think the choice between Mercurial and Git should depend on how large your version control projects are, measured in terms of the number of people working on them simultaneously. If you have a group of a dozen or more working on a single monolithic web application, for example, Git's more powerful branch management tools will make it a much better fit for your project. On the other hand, if your team is developing a heterogeneous distributed system, with only one or two developers working on any one component at any one time, using a Mercurial repository for each of the component projects will allow development to proceed more smoothly with less repository management overhead.

底线:如果你有一个庞大的团队在开发一个庞大的应用程序,那就使用Git;如果您的单个应用程序很小,任何规模都来自于这些应用程序的数量而不是大小,请使用Mercurial。