Stack Overflow问题 How and/or why is merging in Git better than in SVN? 是一个很棒的问题,有一些很棒的答案。但是,它们都没有显示一个简单的例子,其中Git中的合并比SVN更好。
关于这个问题将被重复关闭的可能性是什么
几点:
答案 0 :(得分:22)
从实践的角度来看,由于我称之为“大爆炸合并”问题,合并传统上一直很“困难”。假设一个开发人员一段时间以来一直在研究某些代码并且还没有提交他们的工作(也许开发人员习惯于在针对trunk
的Subversion中工作,并且不提交未完成的代码)。当开发人员最终提交时,将会有很多更改汇总到一个提交中。对于想要将他们的工作与这个“大爆炸”提交合并的其他开发人员来说,VCS工具不会有足够的信息来了解第一个开发人员如何达到他们所提交的点,所以你只是得到“这是一个巨大的冲突在整个功能中,请修复它。“
另一方面,使用Git和其他具有廉价本地分支的DVCS的常用风格是定期提交的。一旦你完成了一些非常有意义的工作,你就可以了。它不一定是完美的,但它应该是一个连贯的工作单元。当您返回合并时,您仍然拥有较小提交的历史记录,显示您从原始状态到当前状态的如何。当DVCS将其与其他人的工作合并时,它有更多关于何时做出更改的信息,并且最终得到更小的和更少的冲突。
关键是你仍然可以通过在你完成某些事情之后制作一个大爆炸提交来与Git合并一个难题。 Git鼓励你做出较小的提交(尽可能使它们尽可能轻松),这将使未来的合并变得更容易。
答案 1 :(得分:15)
我只能告诉你一个小实验,Git并不比Subversion好(同样的问题)。
我想知道这个案子: 你从两个分支开始" mytest1"和" mytest2"两者都基于相同的提交。 你有一个包含函数blub()的C文件。 在分支mytest1中你移动" blub()"到文件中的不同位置并提交。 在分支mytest2中,您修改blub()并提交。 在分支mytest2上,您尝试使用" git merge mytest1"。
似乎给出合并冲突。 我希望Git能够认识到" blub()"在mytest1中被移动,然后能够将mytest2中的修改与mytest1中的移动自动合并。但至少在我尝试时这并没有自动起作用......
因此,虽然我完全理解Git在跟踪已合并的内容以及尚未合并的内容方面要好得多,但我也想知道是否有" pure"合并Git优于SVN的情况...
现在因为这个问题一直困扰着我很长一段时间,我真的试图创建一个具体的例子,其中Git 更好,而SVN中的合并失败。
我在https://stackoverflow.com/a/2486662/1917520找到了一个,但这包括重命名,这里的问题是没有重命名的情况。
所以这是一个SVN示例,基本上尝试这个:
bob +-----r3----r5---r6---+
/ / \
anna / +-r2----r4----+--+ \
/ / \ \
trunk r1-+-------------------r7-- Conflict
这里的想法是:
这是一个Bash脚本,它会产生这种冲突(使用SVN 1.6.17和SVN 1.7.9):
#!/bin/bash
cd /tmp
rm -rf rep2 wk2
svnadmin create rep2
svn co file:///tmp/rep2 wk2
cd wk2
mkdir trunk
mkdir branches
echo -e "A\nA\nB\nB" > trunk/f.txt
svn add trunk branches
svn commit -m "Initial file"
svn copy ^/trunk ^/branches/anna -m "Created branch anna"
svn copy ^/trunk ^/branches/bob -m "Created branch bob"
svn up
echo -e "A\nMA\nA\nB\nB" > branches/anna/f.txt
svn commit -m "anna added text"
echo -e "A\nMB\nA\nB\nMB\nB" > branches/bob/f.txt
svn commit -m "bob added text"
svn up
svn merge --accept postpone ^/branches/anna branches/bob
echo -e "A\nMAB\nA\nB\nMB\nB" > branches/bob/f.txt
svn resolved branches/bob/f.txt
svn commit -m "anna merged into bob with conflict"
svn up
svn merge --reintegrate ^/branches/anna trunk
svn commit -m "anna reintegrated into trunk"
svn up
svn merge --reintegrate --dry-run ^/branches/bob trunk
最后一次" - 干跑"告诉你,会有冲突。如果您首先尝试将Anna的重新整合合并到Bob的分支中,那么您也会遇到冲突;所以如果你用
替换最后一个svn merge
svn merge ^/trunk branches/bob
这也显示了冲突。
这与Git 1.7.9.5相同:
#!/bin/bash
cd /tmp
rm -rf rep2
mkdir rep2
cd rep2
git init .
echo -e "A\nA\nB\nB" > f.txt
git add f.txt
git commit -m "Initial file"
git branch anna
git branch bob
git checkout anna
echo -e "A\nMA\nA\nB\nB" > f.txt
git commit -a -m "anna added text"
git checkout bob
echo -e "A\nMB\nA\nB\nMB\nB" > f.txt
git commit -a -m "bob added text"
git merge anna
echo -e "A\nMAB\nA\nB\nMB\nB" > f.txt
git commit -a -m "anna merged into bob with conflict"
git checkout master
git merge anna
git merge bob
f.txt的内容改变如下。
初始版本
A
A
B
B
安娜的修改
A
MA
A
B
B
鲍勃的修改
A
MB
A
B
MB
B
Anna的分支合并到Bob的分支
之后A
MAB
A
B
MB
B
正如许多人已经指出的那样:问题是,颠覆不记得Bob已经解决了冲突。因此,当您尝试将Bob的分支合并到主干中时,您必须重新解决冲突。
Git完全不同。这里有一些图形表示git正在做什么
bob +--s1----s3------s4---+
/ / \
anna / +-s1----s2----+--+ \
/ / \ \
master s1-+-------------------s2----s4
s1 / s2 / s3 / s4是git工作目录的快照。
注意:
这是" git ref-log"的输出。这显示了所有这些:
88807ab HEAD@{0}: merge bob: Fast-forward
346ce9f HEAD@{1}: merge anna: Fast-forward
15e91e2 HEAD@{2}: checkout: moving from bob to master
88807ab HEAD@{3}: commit (merge): anna merged into bob with conflict
83db5d7 HEAD@{4}: commit: bob added text
15e91e2 HEAD@{5}: checkout: moving from anna to bob
346ce9f HEAD@{6}: commit: anna added text
15e91e2 HEAD@{7}: checkout: moving from master to anna
15e91e2 HEAD@{8}: commit (initial): Initial file
从中可以看出:
使用" git cat-file HEAD @ {8} -p"您可以检查初始提交对象的完整详细信息。对于上面的例子,我得到了:
tree b634f7c9c819bb524524bcada067a22d1c33737f
author Ingo <***> 1475066831 +0200
committer Ingo <***> 1475066831 +0200
Initial file
&#34;树&#34;线标识 快照s1(== b634f7c9c819bb524524bcada067a22d1c33737f) 这个提交所涉及的内容。
如果我这样做&#34; git cat-file HEAD @ {3} -p&#34;我明白了:
tree f8e16dfd2deb7b99e6c8c12d9fe39eda5fe677a3
parent 83db5d741678908d76dabb5fbb0100fb81484302
parent 346ce9fe2b613c8a41c47117b6f4e5a791555710
author Ingo <***> 1475066831 +0200
committer Ingo <***> 1475066831 +0200
anna merged into bob with conflict
上面显示了提交对象,bob在合并anna的开发分支时创建的。再次&#34;树&#34; line指的是创建的快照(此处为s3)。另外请注意&#34;父母&#34;线。第二个以&#34;父母346ce9f&#34;开头。后来告诉git,当你试图将bob的开发分支合并到master分支中时,bob的最后一次提交将anna的最后一次提交作为祖先。这就是为什么git知道将bob的开发分支合并到主分支中是一个快速前进的&#34;。
答案 2 :(得分:6)
我没有具体的例子,但任何类型的 重复合并 都很困难,特别是所谓的 criss-cross merge
a
/ \
b1 c1
|\ /|
| X |
|/ \|
b2 c2
合并b2和c2
Subversion Wiki上的wiki页面描述了基于 merge info 的不对称Subversion合并(带有'sync'和'reintegrate'指示)和 merge 的对称合并有一个“Symmetric Merge with Criss-Cross Merge”
部分答案 3 :(得分:4)
我能想到的最具体的例子是最简单的合并,不会导致合并冲突。然而(TL; DR)与该示例相比,Git本质上仍然是比Subversion更简单的过程。让我们回顾一下原因:
在subversion中考虑以下场景;主干和功能分支:
1 2 3
…--o--o--o trunk
\4 5
o--o branches/feature_1
要合并,您可以在subversion中使用以下命令:
# thank goodness for the addition of the --reintegrate flag in SVN 1.5, eh?
svn merge --reintegrate central/repo/path/to/branches/feature_1
# build, test, and then... commit the merge
svn commit -m "Merged feature_1 into trunk!"
在subversion中合并更改需要另一次提交。这是为了发布合并所做的更改,将功能分支虚拟目录上的更改应用回主干。这样每个使用主干的人现在都可以使用它,修订图看起来像这样:
1 2 3 6
…--o--o--o------o /trunk
\4 5/
o--o /branches/feature_1
让我们看看如何在git中完成。
在Git中,这个合并提交实际上没有必要,因为分支是修订图上的美化书签。因此,使用相同类型的修订图结构,它看起来像这样:
v-- master, HEAD
1 2 3
…--o--o--o
\4 5
o--o
^-- feature_branch
当头部位于主分支上时,我们可以执行与功能分支的简单合并:
# Attempt a merge
git merge feature_branch
# build, test, and then... I am done with the merge
...它将快速转发分支到功能分支所指向的提交。这是可能的,因为Git知道合并的目标是直接后代,而当前分支只需要接受发生的所有更改。修订图最终将如下所示:
1 2 3 4 5
…--o--o--o--o--o
^-- feature_branch, master, HEAD
更改不需要新的提交,因为所有git都已将分支引用进一步移到前面。剩下的就是将其发布到公共存储库(如果您有):
# build, test, and then... just publish it
git push
鉴于这个简单的场景,你可以在Subversion和Git之间区别两件事:
鉴于这是最简单的合并场景,很难说颠覆比git更容易。
在更困难的合并方案中,git还为您提供rebase the branch能够以历史记录重写为代价生成更简单的修订图的功能。一旦掌握了它,并避免发布已经发布的事物的历史记录重写,那么事情并不是那么糟糕。
Interactive rebases超出了问题的范围,但说实话;它使您能够重新排列,挤压和删除提交。我不愿意切换回Subversion,因为历史重写是不可能的。
答案 4 :(得分:2)
保持答案简短 - 在DVCS中,由于你有一个本地源代码控制,如果在合并过程中出现问题(可能会在大型合并中发生),你可以随时回滚到以前的本地版本您在合并之前所做的更改,然后再试一次。
所以基本上你可以做到合并,而不必担心你的本地变化可能在此过程中受到损害。
答案 5 :(得分:1)
如果排除“Merge-Refactored Hell”,将无法获得 合格样本,因为它们不存在
答案 6 :(得分:0)
在Git中合并比在SVN中更容易看起来是一个神话......
例如,与SVN不同,Git无法合并到具有更改的工作树中。
考虑以下简单场景: you have some changes in your working tree and want to integrate remote changes without committing your own 的
SVN :update
,[解决冲突]。
Git :stash
,fetch
,rebase
,stash pop
,[解决冲突],[stash drop
如果有冲突。
或者你知道Git中更简单的方法吗?
Btw,这个用例似乎非常重要,IntelliJ甚至为Git实现了缺失的“更新项目”功能(类似于SVN更新),可以自动执行上述手动步骤: