如何判断在一个有两个父节点的 Git 提交中发生了什么,而这两个父节点并没有合并第二个父节点的更改?

16
在Gitk中,我可以看到一个团队成员的提交(X),它有两个父提交:第一个父提交是他自己之前的提交(A),另一个父提交包含了许多其他人的提交(1到5)。在他的合并之后,其他人(1到5和其他人)所做的所有更改在X,B,C等处不再存在。
A------------
              \
               X - B - C 
              /
1--2--3--4--5
           /
e--r--j--k
     /
l--m

如果我将提交X与提交A进行比较,它显示没有任何差异;如果我将提交X与提交5进行比较,它显示所有缺失的更改。此外,在提交X、B或C时,git log不显示在提交1到5中对文件进行的更改。然而,如果我使用git log --full-history命令,历史记录将显示在提交1到5中所做的更改,但这些更改仍未出现在实际文件中,历史记录也没有显示它们被撤消。因此,git log --full-history似乎与当前文件内容相矛盾。
我与进行提交X的用户进行了交谈。他表示在相关时间内没有进行重置或变基,并且没有撤销任何提交。然而,他说有时会执行git pull origin master命令,结果是将其他人的更改放入他的索引或工作树中,就好像是他自己进行了这些更改,而不是实际的更改作者。他说当发生这种情况时,他会进行全新的克隆,并且不会将本地仓库中的任何内容推送到主分支,因为他认为Git出现了问题。
  • 这两件事情有关系吗(不良的拉取和不良的合并)?

  • 我如何准确地判断发生了什么,以便我们将来可以避免这种情况?

  • 是什么原因导致Git有时将从origin master拉取的更改放置在本地工作目录或索引中,就像它们是本地更改一样?


A看起来像是在自己的历史中悬而未决。A和1有共同的祖先吗? - Schwern
是的,所有的分支都有共同的祖先,我只是在我的草图中省略了它。 - DAC
2个回答

21
然而他说,他有时会执行pull origin master,导致其他人的更改被放入他的索引或工作树中,好像是他进行了这些更改,而不是那些实际的更改作者。听起来他遇到了合并冲突,但不理解它们是什么。这是一个极其常见的问题,不幸的是,我们还不知道如何有效避免它(例如,回到SVN也无法避免它)。

究竟怎么会发生这种情况呢?让我们称你的开发者为Alice和Bob。Alice提交了1-5次提交,而Bob提交了A和X。以下是可能的历史:

1. Bob提交了A。
2. Alice提交了1-5,然后将它们推送到中央仓库。
3. Bob尝试推送A,但由于他的仓库不是最新的,所以无法推送。
$ git push
 ! [rejected]        master -> master (non-fast-forward)
  • 接着Bob会按照你告诉他的去做:首先是拉取代码。然而,由于提交A和提交1-5都修改了同一部分代码,所以他遇到了合并冲突。

  • $ git pull
    Auto-merging file.txt
    CONFLICT (content): Merge conflict in file.txt
    Automatic merge failed; fix conflicts and then commit the result.
    
  • Bob在他的工作目录中看到了其他人的更改,但不理解这些更改是为什么存在的。

  • $ git status
        both modified:   file.txt
    
    他认为Git出了问题,实际上是Git要求他解决合并冲突。他尝试检出新的副本,但是出现了错误:
  • $ git checkout HEAD file.txt  
    error: path 'file.txt' is unmerged
    
  • 由于它无法正常工作,他尝试使用-f

  • $ git checkout -f HEAD file.txt
    warning: path 'file.txt' is unmerged
    
  • 成功!他提交并推送。

    $ git commit
    $ git push
    

    越来越难的地方

    有很多Git工具。 Visual Studio和Xcode都附带了Git集成,还有其他几个GUI,甚至有多个命令行客户端。人们在描述他们如何使用Git时也往往不太严谨,大多数开发者对Git的工作原理并不是非常熟悉。

    不久前,有一篇关于这个主题的优秀论文(我很难找到它)。其中的一些结论是(请原谅我的记忆):

    • 大多数开发者并不真正知道如何使用源代码控制,除了一些非常简单的命令(提交、推送)。

    • 当源代码控制不按开发者的期望运作时,他们会采取各种策略,例如复制粘贴一些他们不太理解的命令来“修复问题”,添加-f标志或删除存储库并从干净的副本重新开始。

    • 在开发团队中,通常只有领导开发者真正知道仓库中正在发生什么。

    所以这真的是一个教育挑战。

    我认为Bob需要学习的关键课程是git pull其实只是git fetchgit merge,即使没有报告冲突,你也可能会遇到合并冲突,需要以非常谨慎和有目的的方式解决它们。即使没有报告冲突,你也需要以非常谨慎和有目的的方式解决合并。这同样适用于没有冲突报告的情况...但现在先不要让Bob感到太过惊讶!

    另一个关键课程是领导开发者需要花时间确保团队中的每个人都能正确地使用源代码控制,并理解拉取、推送、分支和合并之间的关系。这是一个举办午餐讲座的绝佳机会:准备一些幻灯片,买披萨,然后谈论Git的工作原理。


  • 2
    我认为Bob知道如何解决合并冲突,但是既然你怀疑这一点,我会调查一下当冲突出现时Bob实际上做了什么。之后再评论。 - DAC
    1
    他可能知道如何解决合并冲突,但有些人不知道当你执行git pull时会遇到合并冲突,而有些人在Git执行他们不理解的操作时会运行越来越鲁莽的命令。 - Dietrich Epp
    1
    进一步的讨论导致用户回忆起他可能确实进行了 rebase 操作,然后再进行了 push。他还说,当 pull 操作在他的工作树中产生其他人的更改时,他会相信 Git 做错了什么,然后重新克隆,并使用 IDE 的冲突解决 GUI 工具。 - DAC
    3
    我已经意识到通常会发生的情况。当“pull origin master”因冲突而失败时,“git status”将显示其他分支的所有更改已准备好提交。Bob认为那些不是他的更改,开始惊慌失措并取消暂存的东西。但实际上,Bob必须在解决冲突后提交所有这些其他人的更改,否则git将永远不会合并它们。解决方案是告诉Bob,在出现冲突时,解决冲突并提交更改。不要担心你暂存的其他人的更改,这是正常的。 - DAC
    9
    你好,我是Bob。 - Betlista

    1
    有几种方法可以使事物在它们的索引中。拉取是获取然后合并。该合并可能会导致冲突,这将显示为您在索引中的其他人的更改所描述的。不了解冲突管理的用户可能会造成很大的损失,结果可能是错误的合并。
    否则,他们必须通过git pull传递额外的标志,如--no-commit,以使其按照他们的描述行事。
    以下是我如何调查的方法...
    用户以不报告所有信息而出名。当问题发生时,我会找出他们正在做什么,并要求他们在发生问题时复制他们的终端历史记录。他们的shell历史记录或reflog也可能很有趣。
    检查他们的配置。我会查看他们的~/.gitconfig、project/.git/config和env | grep GIT,看看是否有任何有趣的东西。
    我还会找出他们是在命令行上使用git还是一些工具,该工具可能会引起问题。
    找出他们正在使用的git版本,也许它是一个旧的或有缺陷的版本(尽管我还没有遇到过由git bug引起的情况)。
    检查他们的遥控器,可能他们已经混入了其他仓库。
    这个仓库有任何钩子吗?如果有,他们是否使用的工具在用户的计算机上可能无法正常工作?

    该用户知道合并决策的重要性,并具有正确解决问题的历史。但是,此用户使用多个不同的工具,包括git bash来进行pull和push操作,以及NetBeans客户端来提交代码。当用户明天回到工作时,我将检查他的环境变量和工具设置。 - DAC

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