真的,在Git中合并的一个具体例子比SVN更容易吗?

rip*_*234 58 svn git merge

Stack Overflow问题如何和/或为什么在Git中合并比在SVN中更好?是一个很好的问题,有一些很好的答案.然而,它们都没有显示一个简单的例子,其中Git中的合并比SVN更好.

关于这个问题将被重复关闭的可能性是什么

  1. 具体的合并方案
  2. SVN有多难?
  3. 如何在Git中更容易合并?

几点:

  • 对于什么是DVCS而言,没有任何理念或深刻的解释.这些都很棒,但是我不希望他们的细节混淆了这个(恕我直言)的答案
  • 我现在不关心"历史性的SVN".请将现代G​​it(1.7.5)与现代SVN(1.6.15)进行比较.
  • 请不要重命名 - 我知道Git会检测重命名和移动,而SVN则不会.这很好,但我正在寻找更深层次的东西,以及不涉及重命名或移动的示例.
  • 没有rebase或其他'高级'Git操作.请告诉我合并.

Gre*_*ill 22

实际角度来看,由于我称之为"大爆炸合并"问题,合并传统上一直很"困难".假设开发人员一段时间以来一直在研究某些代码并且尚未提交他们的工作(也许开发人员习惯于在Subversion中工作trunk,并且不会提交未完成的代码).当开发人员最终提交时,将会有很多更改汇总到一个提交中.对于想要将他们的工作与这个"大爆炸"提交合并的其他开发人员来说,VCS工具不会有足够的信息来了解第一个开发人员如何达到他们所提交的点,所以你只是得到"这是一个巨大的冲突在这整个功能中,去解决它".

在另一方面,通常的风格与Git和具有廉价的地方分支机构等DVCS工作,是定期提交.一旦你完成了一些非常有意义的工作,你就可以了.它不一定是完美的,但它应该是一个连贯的工作单元.当你回来的合并,你仍然有小的提交,历史,显示了如何从原始状态到了目前的状态.当DVCS去与其他人的工作合并这一点,它有什么变化时做了很多的更多信息,以及你最终得到更小更少的冲突.

关键是你仍然可以通过在你完成一些事情之后制作一个大爆炸提交来与Git合并一个难题.Git鼓励你做出较小的提交(通过使它们尽可能无痛),这使得未来的合并变得更容易.

  • 使用Subversion,我使用功能分支来获取git具有本地分支的功能.很多人说你不能在Subversion中完成未完成的工作 - 但事实并非如此.我可以每天使用Subversion做同样的事情而且它并不贵.考虑到合并,我认为没有任何优势. (8认同)
  • 也许在Darcs中确实如此,但在Git中并非如此,因为合并的Git在通常情况下仅考虑三个版本:我们的,他们的和祖先(合并基础). (3认同)

Ing*_*man 16

我只能告诉你一个小实验,Git并不比Subversion好(同样的问题).

我想知道这种情况:你从两个分支"mytest1"和"mytest2"开始,都是基于相同的提交.你有一个包含函数blub()的C文件.在分支mytest1中,您将"blub()"移动到文件中的不同位置并提交.在分支mytest2中,您修改blub()并提交.在分支mytest2上你尝试使用"git merge mytest1".

似乎给出了合并冲突.我希望Git会认识到"blub()"在mytest1中被移动,然后能够将mytest2中的修改与mytest1中的移动自动合并.但至少在我尝试时这并没有自动起作用......

因此,虽然我完全理解Git在跟踪已合并的内容和尚未合并的内容方面要好得多,但我也想知道是否存在Git优于SVN的"纯粹"合并案例...

现在因为这个问题已经困扰了我很长一段时间,我真的试图创建一个Git 更好的具体例子,而SVN中的合并失败了.

我在这里找到一个/sf/answers/174066371/,但这包括重命名,这里的问题是没有重命名的情况.

所以这是一个SVN示例,基本上尝试这个:

bob        +-----r3----r5---r6---+
          /                /      \
anna     /  +-r2----r4----+--+     \
        /  /                  \     \
trunk  r1-+-------------------r7-- Conflict
Run Code Online (Sandbox Code Playgroud)

这里的想法是:

  • Anna和Bob都是拥有自己分支的开发人员(在r2,r3中创建).
  • 安娜做了一些修改(r4),
  • Bob做了一些修改(r5).
  • Bob将Anna的修改合并到他的分支中; 这会产生冲突,Bob修复然后提交(r6).
  • Annas修改合并回主干(r7).
  • 鲍勃试图将他的修改合并回主干,这再次引发冲突.

这是一个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
Run Code Online (Sandbox Code Playgroud)

最后一次"--Dry-run"告诉你,会有冲突.如果您首先尝试将Anna的重新融合并入Bob的分支,那么您也会遇到冲突; 所以如果你替换最后svn merge

svn merge ^/trunk branches/bob
Run Code Online (Sandbox Code Playgroud)

这也显示出冲突.

这与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
Run Code Online (Sandbox Code Playgroud)

f.txt的内容改变如下.

初始版本

A
A
B
B
Run Code Online (Sandbox Code Playgroud)

安娜的修改

A
MA
A
B
B
Run Code Online (Sandbox Code Playgroud)

鲍勃的修改

A
MB
A
B
MB
B
Run Code Online (Sandbox Code Playgroud)

安娜的分支合并到鲍勃的分支后

A
MAB
A
B
MB
B
Run Code Online (Sandbox Code Playgroud)

正如许多人已经指出的那样:问题是,颠覆不记得鲍勃已经解决了冲突.因此,当您尝试将Bob的分支合并到主干中时,您必须重新解决冲突.

Git完全不同.这里有一些图形表示git正在做什么

bob         +--s1----s3------s4---+
           /                /      \
anna      /  +-s1----s2----+--+     \
         /  /                  \     \
master  s1-+-------------------s2----s4
Run Code Online (Sandbox Code Playgroud)

s1/s2/s3/s4是git工作目录的快照.

笔记:

  • 当安娜和Bob创建自己的开发分支,这将 创造条件,混帐任何承诺.git只会记住两个分支最初都引用与主分支相同的提交对象.(此提交依次将引用s1快照).
  • 当anna实现她的修改时,这将创建一个新的快照"s2"+一个提交对象.提交对象包括:
    • 对快照的引用(此处为s2)
    • 提交消息
    • 有关祖先的信息(其他提交对象)
  • 当bob实现他的修改时,这将创建另一个快照s3 +一个提交对象
  • 当bob将annas修改合并到他的开发分支时,这将创建另一个快照s4(包含他的更改和anna的更改的合并)+另一个提交对象
  • 当anna将她的更改合并回主分支时,这将是所示示例中的"快进"合并,因为在此期间主服务器没有更改.这里的"快进"意味着,主人将简单地指向来自安娜的s2快照,而不会合并任何东西.有了这样的"快进",甚至不会有另一个提交对象."master"分支现在直接引用"anna"分支的最后一次提交
  • 当bob现在将他的更改合并到主干中时,将发生以下情况:
    • git会发现创建s2快照的anna的提交是bobs commit的(直接)祖先,它创建了s4快照.
    • 因为这个git将再次"快进"主分支到"bob"分支的最后一次提交.
    • 再次,这甚至不会创建一个新的提交对象."master"分支将简单地指向"bob"分支的最后一次提交.

这是"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
Run Code Online (Sandbox Code Playgroud)

从这个可以看出:

  • 当我们去anna的开发分支(HEAD @ {7})时,我们不会改变为不同的提交,我们保持提交; git只记得我们现在在另一个分支上
  • 在HEAD @ {5},我们转移到bob的初始分支; 这会将工作副本移动到与master分支相同的状态,因为bob还没有改变任何东西
  • 在HEAD @ {2}我们回到主分支,所以对于同一个提交对象,一切都从一开始.
  • Head @ {1},HEAD @ {0}显示"快进"合并,不会创建新的提交对象.

使用"git cat-file HEAD @ {8} -p",您可以检查初始提交对象的完整详细信息.对于上面的例子,我得到了:

tree b634f7c9c819bb524524bcada067a22d1c33737f
author Ingo <***> 1475066831 +0200
committer Ingo <***> 1475066831 +0200

Initial file
Run Code Online (Sandbox Code Playgroud)

"tree"行标识此提交引用的快照s1(== b634f7c9c819bb524524bcada067a22d1c33737f).

如果我做"git cat-file HEAD @ {3} -p"我得到:

tree f8e16dfd2deb7b99e6c8c12d9fe39eda5fe677a3
parent 83db5d741678908d76dabb5fbb0100fb81484302
parent 346ce9fe2b613c8a41c47117b6f4e5a791555710
author Ingo <***> 1475066831 +0200
committer Ingo <***> 1475066831 +0200

anna merged into bob with conflict
Run Code Online (Sandbox Code Playgroud)

上面显示了提交对象,bob在合并anna的开发分支时创建的."tree"行再次引用创建的快照(此处为s3).另外请注意"父"行.以"parent 346ce9f"开头的第二个告诉git,当你试图将bob的开发分支合并到master分支时,bob的最后一次提交将anna的最后一次提交作为祖先.这就是为什么git知道将bob的开发分支合并到主分支是一个"快进"的原因.

  • 好的,我的答案可能令人困惑:第一部分是关于纯合并的实验,其中git + svn是相同的(在一个分支中,您移动了函数,在另一个分支中,您修改了函数,当您合并git + svn时,两者都给出了一个冲突)。第二部分(您所指)实际上回答了所提出的问题:它给出了一个示例,其中git绝对比svn好。所以是的:答案的第二部分演示了git更好的情况。 (2认同)

Jak*_*ski 6

我没有具体的例子,但任何类型的重复合并都很困难,特别是所谓的纵横交错.

   a
  / \
 b1  c1
 |\ /|
 | X |
 |/ \|
 b2  c2
Run Code Online (Sandbox Code Playgroud)

合并b2和c2


Subversion Wiki上的wiki页面描述了基于合并信息的不对称Subversion合并(带有'sync'和'reintegrate'方向)和基于合并跟踪的DVCS中的对称合并之间的差异,其中有一节" 与Criss-Cross Merge对称合并 "