简体   繁体   English

合并:Hg / Git与SVN

[英]Merging: Hg/Git vs. SVN

I often read that Hg (and Git and...) are better at merging than SVN but I have never seen practical examples of where Hg/Git can merge something where SVN fails (or where SVN needs manual intervention). 我经常读到Hg(和Git和......)在合并方面比SVN更好但是我从未见过Hg / Git可以合并SVN失败的地方(或者SVN需要人工干预的地方)的实际例子。 Could you post a few step-by-step lists of branch/modify/commit/...-operations that show where SVN would fail while Hg/Git happily moves on? 您可以发布一些分支/修改/提交/ ....-操作的逐步列表,显示SVN在Hg / Git愉快地移动时会失败的位置吗? Practical, not highly exceptional cases please... 实用,非常特殊的情况请...

Some background: we have a few dozen developers working on projects using SVN, with each project (or group of similar projects) in its own repository. 一些背景:我们有几十个开发人员在使用SVN进行项目,每个项目(或一组类似项目)都在自己的存储库中。 We know how to apply release- and feature-branches so we don't run into problems very often (ie, we've been there, but we've learned to overcome Joel's problems of "one programmer causing trauma to the whole team" or "needing six developers for two weeks to reintegrate a branch"). 我们知道如何应用发布和功能分支,所以我们不会经常遇到问题(即,我们一直在那里,但我们已经学会克服Joel的问题 “一个程序员给整个团队造成创伤”或“需要六个开发人员两周才能重新整合分支机构”)。 We have release-branches that are very stable and only used to apply bugfixes. 我们的发布分支非常稳定,仅用于应用错误修正。 We have trunks that should be stable enough to be able to create a release within one week. 我们的中继线应该足够稳定,能够在一周内创建发布。 And we have feature-branches that single developers or groups of developers can work on. 我们还有一些开发人员或开发人员可以使用的功能分支。 Yes, they are deleted after reintegration so they don't clutter up the repository. 是的,它们在重新集成后被删除,因此它们不会使存储库混乱。 ;) ;)

So I'm still trying to find the advantages of Hg/Git over SVN. 所以我仍然试图找到Hg / Git优于SVN的优势。 I'd love to get some hands-on experience, but there aren't any bigger projects we could move to Hg/Git yet, so I'm stuck with playing with small artificial projects that only contain a few made up files. 我很想获得一些实践经验,但是还没有任何更大的项目我们可以转移到Hg / Git,所以我一直在玩那些只包含一些组成文件的小型人工项目。 And I'm looking for a few cases where you can feel the impressive power of Hg/Git, since so far I have often read about them but failed to find them myself. 而且我正在寻找一些你可以感受到Hg / Git令人印象深刻的力量的案例,因为到目前为止我经常读到它们但却未能自己找到它们。

I too have been looking for a case where, say, Subversion fails to merge a branch and Mercurial (and Git, Bazaar, ...) does the right thing. 我也一直在寻找一个案例,比如说,Subversion无法合并分支,而Mercurial(和Git,Bazaar,......)做对了。

The SVN Book describes how renamed files are merged incorrectly . SVN Book 描述了重命名文件的合并方式不正确 This applies to Subversion 1.5 , 1.6 , 1.7 , and 1.8 ! 这适用于颠覆1.51.61.71.8 I have tried to recreate the situation below: 我试图重新创建以下情况:

cd /tmp
rm -rf svn-repo svn-checkout
svnadmin create svn-repo
svn checkout file:///tmp/svn-repo svn-checkout
cd svn-checkout
mkdir trunk branches
echo 'Goodbye, World!' > trunk/hello.txt
svn add trunk branches
svn commit -m 'Initial import.'
svn copy '^/trunk' '^/branches/rename' -m 'Create branch.'
svn switch '^/trunk' .
echo 'Hello, World!' > hello.txt
svn commit -m 'Update on trunk.'
svn switch '^/branches/rename' .
svn rename hello.txt hello.en.txt
svn commit -m 'Rename on branch.'
svn switch '^/trunk' .
svn merge --reintegrate '^/branches/rename'

According to the book, the merge should finish cleanly, but with wrong data in the renamed file since the update on trunk is forgotten. 根据这本书,合并应该干净利落,但重命名的文件中的数据错误,因为trunk上的更新被遗忘了。 Instead I get a tree conflict (this is with Subversion 1.6.17, the newest version in Debian at the time of writing): 相反,我得到了一个树冲突(这是与Subversion 1.6.17,在撰写本文时Debian中的最新版本):

--- Merging differences between repository URLs into '.':
A    hello.en.txt
   C hello.txt
Summary of conflicts:
  Tree conflicts: 1

There shouldn't be any conflict at all — the update should be merged into the new name of the file. 根本不应该有任何冲突 - 更新应该合并到文件的新名称中。 While Subversion fails, Mercurial handles this correctly: 当Subversion失败时,Mercurial会正确处理这个问题:

rm -rf /tmp/hg-repo
hg init /tmp/hg-repo
cd /tmp/hg-repo
echo 'Goodbye, World!' > hello.txt
hg add hello.txt
hg commit -m 'Initial import.'
echo 'Hello, World!' > hello.txt
hg commit -m 'Update.'
hg update 0
hg rename hello.txt hello.en.txt
hg commit -m 'Rename.'
hg merge

Before the merge, the repository looks like this (from hg glog ): 在合并之前,存储库看起来像这样(来自hg glog ):

@  changeset:   2:6502899164cc
|  tag:         tip
|  parent:      0:d08bcebadd9e
|  user:        Martin Geisler 
|  date:        Thu Apr 01 12:29:19 2010 +0200
|  summary:     Rename.
|
| o  changeset:   1:9d06fa155634
|/   user:        Martin Geisler 
|    date:        Thu Apr 01 12:29:18 2010 +0200
|    summary:     Update.
|
o  changeset:   0:d08bcebadd9e
   user:        Martin Geisler 
   date:        Thu Apr 01 12:29:18 2010 +0200
   summary:     Initial import.

The output of the merge is: 合并的输出是:

merging hello.en.txt and hello.txt to hello.en.txt
0 files updated, 1 files merged, 0 files removed, 0 files unresolved
(branch merge, don't forget to commit)

In other words: Mercurial took the change from revision 1 and merged it into the new file name from revision 2 ( hello.en.txt ). 换句话说:Mercurial从修订版1中获取了更改,并将其合并到修订版2( hello.en.txt )中的新文件名中。 Handling this case is of course essential in order to support refactoring and refactoring is exactly the kind of thing you will want to do on a branch. 处理这种情况当然是必不可少的,以支持重构和重构,这正是你想要在分支上做的事情。

I do not use Subversion myself, but from the release notes for Subversion 1.5: Merge tracking (foundational) it looks like there are the following differences from how merge tracking work in full- DAG version control systems like Git or Mercurial. 我自己并没有使用Subversion,但是从Subversion 1.5发行说明:合并跟踪(基础)看起来,在GID或Mercurial等全DAG版本控制系统中,合并跟踪的工作方式有以下不同之处。

  • Merging trunk to branch is different from merging branch to trunk: for some reason merging trunk to branch requires --reintegrate option to svn merge . 合并主干到分支不同于合并分支到主干:由于某种原因合并主干到分支需要--reintegrate选项到svn merge

    In distributed version control systems like Git or Mercurial there is no technical difference between trunk and branch: all branches are created equal (there might be social difference, though). 在像Git或Mercurial这样的分布式版本控制系统中,主干和分支之间没有技术差异:所有分支都是相同的(尽管可能存在社会差异)。 Merging in either direction is done the same way. 在任一方向上合并都以相同的方式进行。

  • You need to provide new -g ( --use-merge-history ) option to svn log and svn blame to take merge tracking into account. 您需要为svn logsvn blame提供新的-g ( - --use-merge-history )选项以考虑合并跟踪。

    In Git and Mercurial merge tracking is automatically taken into account when displaying history (log) and blame. 在Git和Mercurial中,在显示历史记录(日志)和责备时会自动考虑合并跟踪。 In Git you can request to follow first parent only with --first-parent (I guess similar option exists also for Mercurial) to "discard" merge tracking info in git log . 在Git中,您可以请求仅使用--first-parent (我猜Mercurial也存在类似选项)跟随第一个父级, --first-parentgit log “丢弃”合并跟踪信息。

  • From what I understand svn:mergeinfo property stores per-path information about conflicts (Subversion is changeset-based), while in Git and Mercurial it is simply commit objects that can have more than one parent. 据我所知, svn:mergeinfo属性存储有关冲突的每路径信息(Subversion是基于变更集的),而在Git和Mercurial中,它只是可以拥有多个父对象的提交对象。

  • "Known Issues" subsection for merge tracking in Subversion suggests that repeated / cyclic / reflective merge might not work properly. Subversion中合并跟踪的“已知问题”小节表明重复/循环/反射合并可能无法正常工作。 It means that with the following histories second merge might not do the right thing ('A' can be trunk or branch, and 'B' can be branch or trunk, respectively): 这意味着在以下历史记录中,第二次合并可能不会做正确的事情('A'可以是主干或分支,'B'可以分别是分支或主干):

    \n*---*---x---*---y---*---*---*---M2 <-- A * --- * --- x --- * --- y --- * --- * --- * --- M2 < -  A.\n         \\ \\ / \\ / /\n          --*----M1---*---*---/ <-- B  -  * ---- M1 --- * --- * --- / < -  B.\n

    In the case the above ASCII-art gets broken: Branch 'B' is created (forked) from branch 'A' at revision 'x', then later branch 'A' is merged at revision 'y' into branch 'B' as merge 'M1', and finally branch 'B' is merged into branch 'A' as merge 'M2'. 在上述ASCII艺术被破坏的情况下:分支'B'在修订版'x'处从分支'A'创建(分叉),然后分支'A'在修订版'y'处合并到分支'B'中合并'M1',最后将分支'B'合并为分支'A'作为合并'M2'。

    \n*---*---x---*-----M1--*---*---M2 <-- A * --- * --- x --- * ----- M1  -  * --- * --- M2 < -  A.\n         \\ / / \\ / / \n          \\-*---y---*---*---/ <-- B \\  -  * --- y --- * --- * --- / < -  B.\n

    In the case the above ASCII-art gets broken: Branch 'B' is created (forked) from branch 'A' at revision 'x', it is merged into branch 'A' at 'y' as 'M1', and later merged again into branch 'A' as 'M2'. 在上述ASCII艺术被破坏的情况下:分支'B'在修订版'x'处从分支'A'创建(分叉),它在'y'处合并为分支'A'作为'M1',稍后再次合并为分支'A'作为'M2'。

  • Subversion might not support advanced case of criss-cross merge . Subversion可能不支持纵向交叉合并的高级案例。

    \n*---b-----B1--M1--*---M3 * --- b ----- B1  -  M1  -  * --- M3\n     \\ \\ / / / / / /\n      \\ X / \\ X /\n       \\ / \\ / \\ / \\ /\n        \\--B2--M2--* \\  -  B2  -  M2  -  *\n

    Git handles this situation just fine in practice using "recursive" merge strategy. Git在实践中使用“递归”合并策略处理这种情况。 I am not sure about Mercurial. 我不确定Mercurial。

  • In "Known Issues" there is warning that merge tracking migh not work with file renames, eg when one side renames file (and perhaps modifies it), and second side modifies file without renaming (under old name). “已知问题”中 ,警告合并跟踪不能与文件重命名一起使用,例如,当一方重命名文件(并且可能修改它)时,第二方修改文件而不重命名(在旧名称下)。

    Both Git and Mercurial handle such case just fine in practice: Git using rename detection , Mercurial using rename tracking . Git和Mercurial在实践中都处理了这样的情况:Git使用重命名检测 ,Mercurial使用重命名跟踪

HTH HTH

Without speaking about the usual advantages (offline commits, publication process , ...) here is a "merge" example I like: 在没有谈到通常的优点(离线提交, 发布过程 ......)这里是一个我喜欢的“合并”示例:

The main scenario I keep seeing is a branch on which ... two unrelated tasks are actually developed 我一直看到的主要场景是一个分支......实际上开发了两个不相关的任务
(it started from one feature, but it lead to the development of this other feature. (它从一个功能开始,但它导致了这个其他功能的开发。
Or it started from a patch, but it lead to the development of another feature). 或者它从补丁开始,但它导致另一个功能的开发)。

How to you merge only one of the two feature on the main branch? 如何只合并主分支上的两个功能之一?
Or How do you isolate the two features in their own branches? 或者,您如何在自己的分支中隔离这两个功能?

You could try to generate some kind of patches, the problem with that is you are not sure anymore of the functional dependencies which could have existed between: 您可以尝试生成某种补丁,问题是您不再确定可能存在的功能依赖关系:

  • the commits (or revision for SVN) used in your patches 补丁中使用的提交(或SVN修订版)
  • the other commits not part of the patch 另一个承诺不是补丁的一部分

Git (and Mercurial too I suppose) propose the rebase --onto option to rebase (reset the root of the branch) part of a branch: 我认为Git(以及Mercurial)建议使用rebase --onto选项来重组(重置分支的根)部分:

From Jefromi's post 来自Jefromi的帖子

- x - x - x (v2) - x - x - x (v2.1)
           \
            x - x - x (v2-only) - x - x - x (wss)

you can untangle this situation where you have patches for the v2 as well as a new wss feature into: 你可以解决这种情况,你有v2的补丁以及新的wss功能:

- x - x - x (v2) - x - x - x (v2.1)
          |\
          |  x - x - x (v2-only)
           \
             x - x - x (wss)

, allowing you to: ,允许您:

  • test each branch in isolation to check if everything compile/work as intended 单独测试每个分支以检查所有内容是否按预期编译/工作
  • merge only what you want to main. 仅合并您想要的主要内容。

The other feature I like (which influence merges) is the ability to squash commits (in a branch not yet pushed to another repo) in order to present: 我喜欢的另一个功能(影响合并)是能够压缩提交 (在尚未推送到另一个仓库的分支中)以呈现:

  • a cleaner history 更清洁的历史
  • commits which are more coherent (instead of commit1 for function1, commit2 for function2, commit3 again for function1...) 更一致的提交(而不是function1的commit1,function2的commit2,function1的commit3 ......)

That ensure merges which are a lot easier, with less conflicts. 这确保合并更容易,冲突更少。

We recently migrated from SVN to GIT, and faced this same uncertainty. 我们最近从SVN迁移到GIT,并面临同样的不确定性。 There was a lot of anecdotal evidence that GIT was better, but it was hard to come across any examples. 有很多轶事证据表明GIT更好,但很难找到任何例子。

I can tell you though, that GIT is A LOT BETTER at merging than SVN. 我可以告诉你, GIT在合并时比SVN好很多。 This is obviously anecdotal, but there is a table to follow. 这显然是轶事,但有一个表可供遵循。

Here are some of the things we found: 以下是我们发现的一些事情:

  • SVN used to throw up a lot of tree conflicts in situations where it seemed like it shouldn't. SVN过去常常在不应该出现的情况下抛出很多树冲突。 We never got to the bottom of this but it doesn't happen in GIT. 我们从来没有达到过这个目的,但它并没有发生在GIT中。
  • While better, GIT is significantly more complicated. 虽然更好,但GIT要复杂得多。 Spend some time on training. 花一些时间进行培训。
  • We were used to Tortoise SVN, which we liked. 我们习惯了Tortoise SVN,我们很喜欢。 Tortoise GIT is not as good and this may put you off. Tortoise GIT不是那么好,这可能会让你失望。 However I now use the GIT command line which I much prefer to Tortoise SVN or any of the GIT GUI's. 但是我现在使用GIT命令行,我更喜欢Tortoise SVN或任何GIT GUI。

When we were evaluating GIT we ran the following tests. 当我们评估GIT时,我们运行了以下测试。 These show GIT as the winner when it comes to merging, but not by that much. 这些显示GIT在合并方面是赢家,但不是那么多。 In practice the difference is much larger, but I guess we haven't managed to replicate the situations that SVN handles badly. 在实践中,差异要大得多,但我想我们还没有设法复制SVN处理不当的情况。

GIT与SVN合并评估

Others have covered the more theoretical aspects of this. 其他人已经涵盖了更多的理论方面。 Maybe I can lend a more practical perspective. 也许我可以提供更实际的观点。

I'm currently working for a company that uses SVN in a "feature branch" development model. 我目前正在为一家在“功能分支”开发模型中使用SVN的公司工作。 That is: 那是:

  • No work can be done on trunk 无法在行李箱上完成任何工作
  • Each developer can have create their own branches 每个开发人员都可以创建自己的分支
  • Branches should last for the duration of the task undertaken 分支机构应在任务期间持续使用
  • Each task should have it's own branch 每个任务都应该拥有自己的分支
  • Merges back to trunk need to be authorized (normally via bugzilla) 合并回主干需要授权(通常通过bugzilla)
  • At times when high levels of control are needed, merges may be done by a gatekeeper 在需要高级别控制的情况下,合并可以由网守完成

In general, it works. 一般来说,它的工作原理。 SVN can be used for a flow like this, but it's not perfect. SVN可以用于这样的流程,但它并不完美。 There are some aspects of SVN which get in the way and shape human behaviour. SVN的某些方面妨碍了人类的行为。 That gives it some negative aspects. 这给了它一些消极方面。

  • We've had quite a few problems with people branching from points lower than ^/trunk . 我们遇到了很多问题,人们从低于^/trunk点分支。 This litters merge information records throughout the tree, and eventually breaks the merge tracking. 此垃圾合并整个树中的信息记录,并最终打破合并跟踪。 False conflicts start appearing, and confusion reigns. 虚假冲突开始出现,混乱占主导地位。
  • Picking up changes from trunk into a branch is relatively straight forward. 从干线到分支的接收变化相对简单。 svn merge does what you want. svn merge做你想要的。 Merging your changes back requires (we're told) --reintegrate on the merge command. 合并你的更改需要(我们被告知) --reintegrate在merge命令上重新整合。 I've never truly understood this switch, but it means that the branch can't be merged into trunk again. 我从来没有真正理解这个开关,但这意味着分支不能再次合并到主干。 This means it's a dead branch and you have to create a new one to continue work. 这意味着它是一个死分支,你必须创建一个新分支继续工作。 (See note) (见说明)
  • The whole business of doing operations on the server via URLs when creating and deleting branches really confuses and scares people. 在创建和删除分支时,通过URL在服务器上执行操作的整个过程确实让人感到困惑和恐慌。 So they avoid it. 所以他们避免它。
  • Switching between branches is easy to get wrong, leaving part of a tree looking at branch A, whilst leaving another part looking at branch B. So people prefer to do all their work in one branch. 在分支之间切换很容易出错,留下一部分树看着分支A,而留下另一部分看分支B.所以人们更喜欢在一个分支中完成所有工作。

What tends to happen is that an engineer creates a branch on day 1. He starts his work and forgets about it. 往往会发生的是工程师在第1天创建分支。他开始工作并忘记了它。 Some time later a boss comes along and asks if he can release his work to trunk. 一段时间后,一位老板走了进来,询问他是否可以将他的工作释放到行李箱。 The engineer has been dreading this day because reintegrating means: 工程师一直担心这一天因为重新融合意味着:

  • Merging his long lived branch back into trunk and solving all conflicts, and releasing unrelated code that should have been in a separate branch, but wasn't. 将他长期存在的分支合并回主干并解决所有冲突,并释放应该在单独分支中的不相关代码,但事实并非如此。
  • Deleting his branch 删除他的分支
  • Creating a new branch 创建一个新分支
  • Switching his working copy to the new branch 将其工作副本切换到新分支

...and because the engineer does this as little as they can, they can't remember the "magic incantation" to do each step. ......而且因为工程师尽可能少地做到这一点,他们不记得每一步的“神奇咒语”。 Wrong switches and URLs happen, and suddenly they're in a mess and they go get the "expert". 错误的交换机和URL发生了,突然间它们变得一团糟,他们就会得到“专家”。

Eventually it all settles down, and people learn how to deal with the shortcomings, but each new starter goes through the same problems. 最终一切都安定下来,人们学习如何处理缺点,但每个新的首发都会遇到同样的问题。 The eventual reality (as opposed to what I set out at he start) is: 最终的现实(与我在他开始时提出的相反)是:

  • No work is done on trunk 行李箱上没有工作
  • Each developer has one major branch 每个开发人员都有一个主要分支
  • Branches last until work needs to be released 分支持续到需要释放工作
  • Ticketed bug fixes tend to get their own branch 已售票的错误修复往往会得到自己的分支
  • Merges back to trunk are done when authorized 合并回主干是在授权后完成的

...but... ...但...

  • Sometimes work makes it to trunk when it shouldn't because it's in the same branch as something else. 有时工作会使它成为主干,因为它与其他东西在同一个分支中。
  • People avoid all merging (even easy stuff), so people often work in their own little bubbles 人们避免所有合并(即使是简单的东西),所以人们经常在他们自己的小泡泡中工作
  • Big merges tend to occur, and cause a limited amount of chaos. 大合并往往会发生,并造成有限的混乱。

Thankfully the team is small enough to cope, but it wouldn't scale. 值得庆幸的是,团队足够小,可以应付,但不会扩大规模。 Thing is, none of this is a problem with CVCS, but more that because merges aren't as important as in DVCS they're not as slick. 事实上,这并不是CVCS的问题,但更多的是因为合并不如DVCS那么重要,因此它们不是那么光滑。 That "merge friction" causes behaviour which means that a "Feature Branch" model starts to break down. “合并摩擦”导致行为,这意味着“特征分支”模型开始崩溃。 Good merges need to be a feature of all VCS, not just DVCS. 良好的合并需要成为所有VCS的功能,而不仅仅是DVCS。


According to this there's now a --record-only switch that could be used to solve the --reintegrate problem, and apparently v1.8 chooses when to do a reintegrate automatically, and it doesn't cause the branch to be dead afterwards 根据这个 ,现在有一个--record-only开关可用于解决--reintegrate问题, 显然 v1.8选择何时自动重新整合,并且它不会导致分支死后

Prior to subversion 1.5 (if I'm not mistaken), subversion had a significant dissadvantage in that it would not remember merge history. 在颠覆1.5之前(如果我没有记错的话),颠覆有一个显着的缺点,因为它不会记住合并历史。

Let's look at the case outlined by VonC: 让我们来看看VonC概述的案例:

- x - x - x (v2) - x - x - x (v2.1)
          |\
          |  x - A - x (v2-only)
           \
             x - B - x (wss)

Notice revisions A and B. Say you merged changes from revision A on the "wss" branch to the "v2-only" branch at revision B (for whatever reason), but continued using both branches. 注意修订版A和B.假设您将“wss”分支上的修订版A的更改合并到修订版B中的“仅v2”分支(无论出于何种原因),但继续使用两个分支。 If you tried to merge the two branches again using mercurial, it would only merge changes after revisions A and B. With subversion, you'd have to merge everything, as if you didn't do a merge before. 如果你试图使用mercurial再次合并两个分支,它只会在修订A和B之后合并更改。使用subversion,你必须合并所有内容,就像之前没有合并一样。

This is an example from my own experience, where merging from B to A took several hours due to the volume of code: that would have been a real pain to go through again , which would have been the case with subversion pre-1.5. 这是我自己经验的一个例子,从B到A的合并需要花费几个小时的代码量:这将是一个真正的痛苦再次经历,这可能是颠覆前1.5的情况。

Another, probably more relevant difference in merge behaviour from Hginit: Subversion Re-education : Hginit:Subversion Re-education中合并行为的另一个可能更相关的区别:

Imagine that you and I are working on some code, and we branch that code, and we each go off into our separate workspaces and make lots and lots of changes to that code separately, so they have diverged quite a bit. 想象一下,你和我正在研究一些代码,我们分支这些代码,然后我们分别进入我们各自的工作区,分别对这些代码进行大量的修改,因此它们分歧很大。

When we have to merge, Subversion tries to look at both revisions—my modified code, and your modified code—and it tries to guess how to smash them together in one big unholy mess. 当我们必须合并时,Subversion会尝试查看两个版本 - 我的修改后的代码和修改后的代码 - 它会尝试猜测如何在一个大的邪恶混乱中将它们粉碎在一起。 It usually fails, producing pages and pages of “merge conflicts” that aren't really conflicts, simply places where Subversion failed to figure out what we did. 它通常会失败,产生并非真正冲突的“合并冲突”的页面和页面,只是Subversion无法弄清楚我们做了什么的地方。

By contrast, while we were working separately in Mercurial, Mercurial was busy keeping a series of changesets. 相比之下,当我们在Mercurial中单独工作时,Mercurial正忙着保留一系列变更集。 And so, when we want to merge our code together, Mercurial actually has a whole lot more information: it knows what each of us changed and can reapply those changes, rather than just looking at the final product and trying to guess how to put it together. 因此,当我们想要将我们的代码合并在一起时,Mercurial实际上有更多的信息:它知道我们每个人都改变了什么并且可以重新应用这些更改,而不仅仅是查看最终产品并试图猜测如何放置它一起。

In short, Mercurial's way of analyzing differences is (was?) superior to subversion's. 简而言之,Mercurial分析差异的方式(优于?)优于颠覆。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM