Mercurial和Git有什么区别?

726

我已经使用过Windows上的Git (msysGit) 一段时间了,并且我喜欢分布式源代码控制的理念。最近,我开始看Mercurial (hg),它看起来很有趣。然而,我无法理解hg和git之间的区别。

是否有人进行过git和hg的对比?我想知道hg和git之间的区别,而不必卷入支持者讨论。

25个回答

345

1
“Git vs Mercurial Please Relax”这个问题已经过时了,虽然它和这个问题一样古老。也许现在应该删除并重新打开这个问题,因为这两个工具都已经添加了三年以上的功能。 - gman

237

我在Mercurial上工作,但从根本上来说,我认为两个系统是等价的。它们都使用相同的抽象:一系列快照(changesets),这些快照组成了历史记录。每个changeset都知道它来自哪里(父changeset)并且可以有多个子changesets。最近的hg-git扩展提供了Mercurial和Git之间的双向桥梁,也证明了这一点。

Git强调对这个历史图进行改变(带来的所有后果),而Mercurial并不鼓励重写历史,但仍然可以轻松地实现,而对此所产生的后果正是您应该期望的(也就是说,如果我修改了您已经拥有的一个changeset,则当您从我这里拉取时,您的客户端将看到它是新的)。因此,Mercurial具有偏向于非破坏性命令。

至于轻量级分支,自从……以来,Mercurial就支持具有多个分支的存储库。Git具有多个分支的存储库正是多个发展分岔的开发方向。然后,Git为这些分岔添加名称,并允许您在远程查询这些名称。Mercurial的Bookmarks扩展添加了本地名称,并且在Mercurial 1.6中,您可以在推送/拉取时移动这些书签。

我使用Linux,但显然,TortoiseHg在Windows上比Git更快,更好(由于更好地使用了可怜的Windows文件系统)。http://github.comhttp://bitbucket.org都提供在线托管服务,Bitbucket的服务很棒,响应很快(我没有尝试过github)。

我选择Mercurial,因为它感觉干净而优雅——我对使用Git时得到的Shell / Perl / Ruby脚本感到厌烦。如果您想知道我的意思,请尝试一下git-instaweb.sh文件:这是一个shell脚本,生成一个Ruby脚本,我认为它运行一个Web服务器。 shell脚本生成另一个shell脚本来启动第一个Ruby脚本。当然,还有一点Perl

我喜欢这篇博客文章,它将Mercurial和Git与詹姆斯·邦德(James Bond)和麦克盖尔(MacGyver)进行了比较——Mercurial某种程度上比Git更低调。在我看来,使用Mercurial的人并不容易被吸引。这反映在每个系统如何执行Linus所描述的“最酷的合并!”. 在Git中,您可以通过执行以下操作

git fetch <project-to-union-merge>
GIT_INDEX_FILE=.git/tmp-index git-read-tree FETCH_HEAD
GIT_INDEX_FILE=.git/tmp-index git-checkout-cache -a -u
git-update-cache --add -- (GIT_INDEX_FILE=.git/tmp-index git-ls-files)
cp .git/FETCH_HEAD .git/MERGE_HEAD
git commit

这些命令看起来对我来说很晦涩。在Mercurial中,我们会执行:

hg pull --force <project-to-union-merge>
hg merge
hg commit

注意Mercurial命令非常简单,没有任何特殊之处--唯一不同的是--force标志用于hg pull,因为当你从一个无关的仓库pull时,Mercurial会中止操作。正是这样的差异让我感觉Mercurial更加优雅。


21
请注意,TortoiseHg也可以与Linux上的Gnome文件管理器Nautilus一起使用。 - oenli
4
仅当使用Ruby Web服务器Webrick时,才会生成Ruby脚本。 - alternative
4
和 Git 一样,当你提交时,Mercurial 会存储文件的快照。我不同意重命名只能像 Git 一样使用启发式跟踪,模型中没有任何阻止添加额外信息到快照中,例如重命名信息。我们在 Mercurial 中就这么做了。 - Martin Geisler
63
要使用git合并(pull)一个不相关的项目,你可以简单地执行:git pull <项目的URL>。你引用的邮件来自git的早期阶段(2005年)。 - knittl
5
Knittl:啊,好的,很高兴听到Git变得更少晦涩了。不过,我认为这个例子有点表明了这些系统在我们认为什么是酷的方面的差异,相比Mercurial,Git给我感觉更低级(但并不更强大)。 - Martin Geisler
显示剩余2条评论

73

Git是一个平台,而Mercurial只是一个应用程序。Git是一个带有分布式版本控制系统应用程序的版本化文件系统平台,但像其他平台应用程序一样,它更加复杂,并且具有较粗糙的边缘。但这也意味着,Git的版本控制系统非常灵活,您可以使用Git做许多与源代码控制无关的事情。

这就是区别的本质。

要深入理解Git,从存储库格式开始理解至关重要。Scott Chacon的“Git Talk”对此进行了很好的介绍。如果您不知道发生了什么,就试图使用Git,最终可能会感到困惑(除非您只使用非常基本的功能)。当你只需要一个每天编程例行事务中使用的DVCS时,这听起来可能很愚蠢,但Git的奇妙之处在于,存储库格式实际上非常简单,您可以相当轻松地理解Git的整个操作。

对于一些更加技术性的比较,我个人觉得Dustin Sallings的文章最好:

他实际上广泛使用过两种分布式版本控制系统,并且对它们都很了解-最终选择了git。

78
我经常看到人们提到git是一个“平台”,但这更像是一种理论或普遍的误解,因为除了运行git之外,没有主要的例子可以用它来做其他事情。 - Ian Kelling
@Ian - 如果我没记错的话,Aptana Studio 3 使用它作为其内部更新系统。 - mac
22
简而言之:Mercurial是一种开源的分布式版本控制系统,而Git则是一种生活方式选择。 - Tim Keating

51

主要的差异在于 Windows 上。Mercurial 受到原生支持,Git 则不受支持。你可以通过 bitbucket.org 获得与 github.com 非常相似的托管服务(实际上更好,因为你可以获得免费的私有代码库)。我曾经使用过 msysGit 一段时间,但现在转向了 Mercurial,并非常满意。


64
因此,“本地支持”可能不是完全正确的词语,但可以确定的是它在Windows下得到的支持不太好。 - Ian Kelling
14
Git在Windows上有一定程度的支持。但如果尝试在跨平台情况下使用Unicode文件名,你会遇到麻烦。 - Craig McQueen
@Craig:那只是平台的缺陷。一个有能力的平台应该允许你切换到适合的区域设置。如果你坚持使用utf-8,除了Mac OS X对utf-8的规范化稍有不同外,你会发现基本上没问题,但是我不确定Windows是否允许你使用utf-8... - Arafangion
23
Windows支持Unicode,尽管微软在其API中选择了UTF-16而非UTF-8。尽管如此,Git跨平台支持Unicode仍然是完全可能的。SVN已经很好地解决了这个问题。但目前对于msysGit开发团队来说,并不是一个高优先级的任务。http://code.google.com/p/msysgit/issues/detail?id=80 - Craig McQueen

38

如果你是一位寻找基本离线版本控制的Windows开发人员,则选择Hg。我发现Git难以理解,而Hg则简单,并且与Windows shell 很好地集成。我下载了Hg并遵循了这个教程(hginit.com) - 十分钟后我就有了一个本地仓库,并且可以继续我的项目。


1
我按照相同的教程进行了操作。它是权威的。 - Nathan

22

14
那有什么帮助作用呢?这是否意味着git更像是武术类型的? - fmuecke

20
它们几乎是相同的。从我的角度来看,最重要的区别(我指的是选择一种DVCS而不是另一种的原因)在于这两个程序如何管理分支。
使用Mercurial启动新分支,只需将存储库克隆到另一个目录并开始开发。然后,您进行拉取和合并操作。而使用git,则必须显式地为您想要使用的新主题分支命名,然后您可以使用相同的目录开始编码。
简而言之,在Mercurial中,每个分支都需要自己的目录;而在git中,您通常在单个目录中工作。在Mercurial中切换分支意味着更改目录;在git中,它意味着请求git使用git checkout更改目录的内容。
老实说:我不知道是否可以在Mercurial中做同样的事情,但由于我通常在Web项目上工作,并始终使用相同的目录,因此对我来说使用git更加舒适,因为我无需重新配置Apache和重新启动它,也无需在每次分支时破坏文件系统。
编辑:正如Deestan所指出的,Hg拥有命名分支,它们可以存储在单个存储库中,并允许开发人员在同一个工作副本中切换分支。但是,git分支并不完全与Mercurial命名分支相同:它们是永久的,而不是像在git中一样是可丢弃的分支。这意味着,即使您决定永远不合并它,如果将命名分支用于实验任务,则它将存储在存储库中。这就是为什么Hg鼓励使用克隆进行实验性短期任务和使用命名分支进行长期任务(例如发布分支)的原因。
许多Hg用户更喜欢使用克隆而不是命名分支,原因往往更多是社交或文化方面,而不是技术方面。例如,对于最新版本的Hg,甚至可以关闭命名分支并从每个变更集中递归地删除元数据。
另一方面,git邀请使用“命名分支”,这些分支不是永久的,并且不会作为每个变更集的元数据存储。

从我个人的角度来看,Git 的模型与命名分支和在同一目录中切换分支之间的概念密切相关;Mercurial 可以通过命名分支做到相同的功能,但它鼓励使用克隆,我个人不太喜欢这种方法。


6
这不是区别;Hg 也有命名分支。在正常开发过程中,我们经常使用它们来代替克隆分支。 - Deestan
2
据我所知,Hg 中的命名分支是通过在每个提交中存储元数据来获得的;我可能错了,但我曾经读过,一旦创建了一个分支,它的元数据将嵌入到每个变更集中,并成为历史的一部分。这与 git 有很大的区别。“克隆对于快速实验非常有用,其中您不想记录分支名称,而命名分支则适用于长期分支。”http://tinyurl.com/2wz39qx我的帖子想说的是,git 的标准工作流程鼓励您使用单个工作副本;Hg 标准工作流包括克隆,这不适合我的个人需求。 - Arialdo Martini
有关Git和Hg中分支的相似性/差异的良好解释,请参见:http://mercurial.selenic.com/wiki/GitConcepts#Branch_model - user82216
2
在hg中,命名分支与git中的分支完全不同。在hg中,有一条真正的路径,所有分支都是从该路径上分离出来的。而在git中,没有一条真正的路径,只有存储库的不同状态和指向该状态的指针。每个分支只是一个不同的指针。哪个指针是主要的取决于用户。分支都是对等的。 - gman

18

Git和Mercurial有一个巨大的区别,即它们表示每个提交(commit)的方式。Git将提交视为快照(snapshot),而Mercurial将其表示为差异(diffs)。

这在实践中意味着什么?嗯,在git中许多操作更快,比如切换到另一个提交、比较提交等,尤其是这些提交相距较远时。

据我所知,Mercurial的方法没有任何优势。


29
Changesets(差异)的优点在于占用的空间更少。Git通过压缩来回收提交所使用的空间,但这需要偶尔进行明确的重新压缩步骤(“git pack”)。 - quark
8
现在它被称为“git gc”,但是有不同级别的垃圾回收,一些级别在最近版本的git中会自动执行。 - FelipeC
6
实际上,Mercurial 也使用快照。 - user78110
13
我不理解你在“快照”和“差异”之间划分的区别。hg和git都将提交存储为文件增量(的组)。这些增量基于各自SCM选择的先前修订版。你有数据表明git在你提到的操作中确实更快吗?无论如何,更新到另一个提交所花费的大部分时间都是写入工作目录,而不是读取存储库。 - Kevin
2
“快照”和“差异”——完全无关紧要。然而,存储库在内部的存储方式与用户所看到的内容无关。Git和Mercurial都向用户呈现“快照”。没有理由认为不能将与Git实现方式相同的存储库格式添加到Mercurial中,就像我认为Bazaar所做的那样。 - Mark Booth
显示剩余2条评论

11

如果我正确理解它们(尽管我远非每个专家),这两种基本上有不同的哲学。 我最初使用Mercurial 9个月。现在我已经使用Git 6个月。

Hg是版本控制软件。它的主要目标是跟踪软件版本。

Git是一个基于时间的文件系统。它的目标是为文件系统添加另一个维度。大多数文件和文件夹,Git则添加了时间。这恰好能作为VCS运行是其设计的副产品。

在Hg中,它始终试图维护整个项目历史记录。默认情况下,我认为Hg希望在推送和拉取时获取所有用户对所有对象的所有更改。

在Git中,只有一组对象和这些跟踪文件(分支/头)来确定哪些对象集表示特定状态下文件树。在推送或拉取时,Git仅发送需要的对象,这是所有对象的一个小子集,用于推送或拉取特定的分支。

就Git而言,不存在“1个项目”的概念。您可以在同一个repo中拥有50个项目,Git不会介意。每个项目都可以在同一个repo中单独管理,并且可以很好地运行。

Hg中的分支概念是指从主项目或从分支等分支。Git没有这样的概念。在Git中,分支只是树的状态,所有内容都在分支中。哪个分支是官方的、当前的或最新的在Git中没有意义。

我不知道那是否有意义。如果我可以画图,Hg可能看起来像这样,其中每个提交都是o

             o---o---o
            /        
o---o---o---o---o---o---o---o
         \         /
          o---o---o

具有单个根和从其分支出的树。虽然Git可以这样做,而且人们经常以这种方式使用它,但这并非强制要求。如果有这样一张Git图片,它可能很容易看起来像这样:

o---o---o---o---o

o---o---o---o
         \
          o---o

o---o---o---o

实际上,在某些方面,显示git中的分支甚至没有意义。

有一件非常令人困惑的事情是,Git和Mercurial都有一个叫做“分支”的东西,但它们根本不是同一种东西。在Mercurial中,当不同repo之间存在冲突时,就会出现一个分支。而在Git中,一个分支似乎类似于hg中的克隆。但是,尽管可能具有类似的行为,但克隆绝对不是相同的东西。考虑我使用相当大的Chromium repo在git与hg中尝试这些操作的情况。

$ time git checkout -b some-new-branch
Switched to new branch 'some-new-branch'

real   0m1.759s
user   0m1.596s
sys    0m0.144s

现在使用 hg clone 进行克隆

$ time hg clone project/ some-clone/

updating to branch default
29387 files updated, 0 files merged, 0 files removed, 0 files unresolved.
real   0m58.196s
user   0m19.901s
sys    0m8.957

这两个都是热运行。也就是说,我运行了两次,这是第二次运行。hg clone实际上与git-new-workdir相同。这两个都会创建一个全新的工作目录,几乎就像您键入了cp -r project project-clone一样。这不同于在git中创建一个新分支。它更加重量级。如果有真正等同于git分支的东西,我不知道它是什么。

我从某种程度上理解hg和git 可能能够做类似的事情。如果是这样,那么它们所引导的工作流程仍然存在巨大的差异。在git中,典型的工作流程是为每个功能创建一个分支。

git checkout master
git checkout -b add-2nd-joypad-support
git checkout master
git checkout -b fix-game-save-bug
git checkout master
git checkout -b add-a-star-support

那刚刚创建了三个基于名为master的分支的分支。(我相信在Git中有一些方法可以使它们变成1行而不是2行)

现在只需对其中一个进行操作

git checkout fix-game-save-bug

然后开始工作。提交更改等等。即使在像 Chrome 这样的大型项目中切换分支也几乎是瞬间完成的。我实际上不知道如何在 hg 中做到这一点。这不是我读过的任何教程的一部分。

另一个重要的区别是 Git 的暂存区。

Git 有这个暂存区的概念。你可以将其视为隐藏文件夹。当你提交时,只提交暂存区中的内容,而不是你的工作目录中的更改。这可能听起来很奇怪。如果你想提交工作目录中所有的更改,你可以使用 git commit -a 命令,它会将所有修改过的文件添加到暂存区,然后提交它们。

那么暂存区的作用是什么呢?你可以轻松地将你的提交分开。想象一下,你编辑了 joypad.cpp 和 gamesave.cpp,你想分别提交它们。

git add joypad.cpp  // copies to stage
git commit -m "added 2nd joypad support"
git add gamesave.cpp  // copies to stage
git commit -m "fixed game save bug"

Git甚至有命令来决定你想复制到阶段的同一文件中的哪些特定行,因此您可以将这些提交单独拆分开。为什么要这样做呢?因为作为单独的提交,其他人可以只拉取他们想要的提交,或者如果出现问题,他们可以撤销仅包含问题的提交。


Git甚至有命令来决定您想要复制到阶段的同一文件中的哪些特定行,以便您可以将这些提交分开处理。这些命令是什么? - James McMahon
1
@James:git add --patch,请参考http://linux.die.net/man/1/git-add 或使用 git add -i,例如 https://dev59.com/RnE95IYBdhLWcg3wSsCD。 - tanascius
@gman:不必先检出主分支再使用“checkout -b <name>”创建新分支,你可以直接使用“git branch <name>”来创建一个新分支而不切换到它。 - tanascius

11

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