在git pull之后,在临时区域中进行了许多不相关的修改

时间:2017-03-16 19:34:49

标签: git merge

一位同事一直在master的本地资料库中工作,几周后,他做了git pull。与此同时,master上有一堆活动,包括至少10次合并提交(到目前为止我们一直在使用合并工作流程)。

在拉动之后,有一些文件存在冲突。但出乎意料的是,他的暂存区域有数百个修改他没有工作的文件,而且每个修改都恢复了自共同祖先提交以来origin/master所做的更改。例如:

自共同祖先以来的变化

他的分支(master

  • A:添加3行
  • B:删除2行
  • C:删除1行
  • G1..G99:新文件

origin/master

  • C:添加2行
  • D:删除10行
  • E:更改20行
  • ...:更多文件已更改
  • N1..N3:新文件
  • D1..D5:已删除的文件

git pull

之后

临时区域的修改

  • 答:删除3行
  • B:添加2行
  • G1..G99:新文件
  • origin/master的所有其他更改都已消失。缺少N1-N3,D1-D5又回来了。

冲突中

  • C

我已经复制了这个问题,以确认他除了普通的git pull之外没有做任何特别的事情。

不幸的是他提交了所有这些更改,我最终需要还原该提交。但是我想知道为什么会这样,所以我们可以在将来避免它。

我已阅读some related个问题,但没有人回答这个问题:为什么当相应文件没有变化时,Git会从master恢复更改在他的树枝上?我理解为什么在拉动后发生冲突时会对暂存区域进行修改,但我不明白为什么他们会从共同的祖先那里有效地恢复origin/master上的工作。

我们现在解决问题的方法是

  • 使用git pull --rebase代替合并
  • 在拉动并且不提交后检查状态 如果有无法解释的修改

1 个答案:

答案 0 :(得分:3)

编辑(基于问题编辑了解更多详细信息):这可能是重命名检测出错的情况,并且可能会出现纵横交错的情况。历史也是如此,因此Git正在形成一个虚拟的合并基地"。

要了解,请先运行:

git merge-base --all <hash1> <hash2>

关于错误合并发生时两个分支名称masterorigin/master指向的两个哈希ID。我们需要这个来测试重命名检测是否存在问题,以及是否存在多个合并基础。

如果打印多个哈希ID,您将获得recursive策略来合并合并库,然后使用生成的合并作为新的合并库。这可能(在极少数情况下)产生非常混乱的结果。如果是这样,切换到-s resolve策略可能有所帮助。这将选择两个合并基地之一并坚持下去。 (但是您无法控制使用哪个合并基础。)请注意,将merge.conflictStyle设置为diff3也会sometimes show you the effect of a virtual merge base(但有时只会)

接下来,无论是否存在多个合并库,我们都可以检查重命名检测是否导致问题。如果是这样,有两件事可能会有所帮助:

  • 大锤方法:在合并期间完全禁用重命名检测(需要Git 2.8或更高版本):添加-X no-renames,其匹配git diff的拼写(尽管它有--no-renames }})。
  • 更精细调整的大头钉:提高检测限(默认值为50%):-X rename-threshold=100-X find-renames=100需要精确匹配而不是近似匹配。新拼写-X find-renames=<n>git diff选项的拼写相匹配,是Git 2.8版中的新拼写,但该选项本身非常陈旧,自1.7.4版本开始出现。请注意,其他阈值也是允许的,尽管100%完全匹配非常值得注意。

要确定Git是否检测到重命名,我们需要 合并基础,如果存在多个合并基础,这有点问题:我们必须首先合并合并基础,以获取一个真正的合并提交,Git将用作新的合并基础。我只是假设这是的情况,因为这个过程有点混乱;所以我们继续看看&#34;&#34;&#34;合并基础,使用:

git diff --name-status --find-renames=50 --diff-filter=R <basehash> <hash1>
git diff --name-status --find-renames=50 --diff-filter=R <basehash> <hash2>

<hash1><hash2>值与之前相同。我们告诉Git给我们文件名和状态,然后只打印状态为R(重命名)的文件的名称。如果Git 认为某些文件已重命名,我们将在此处查看其新旧名称。 Git在合并过程中如何将它们结合起来有点棘手,但只有R - 状态文件存在意味着Git会做这种事情。如果有没有文件,那么它毕竟不是重命名检测。

(有关git diff中重命名检测的详细说明,请参阅this answer。合并代码使用不同的命令行选项,其中一些在最近相对更改。请参阅VonC's answer到{{ 3}}以及。)

以下原始答案。

一般情况下,合并时,Git不会选择 &#34; side&#34;。相反,它需要两个方面。请记住,这整个三向合并事件都有第三个​​方:有&#34; s&#34;你的&#34;方(HEAD),&#34;他们的&#34;方(你正在合并)和基础。这形成了一个三角形:

                o    <-- HEAD
             ...
            o
         ...
...--o--B (base)
         ...
            o
             ...
                o   <-- theirs

并且合并将它们全部组合在一起以制作闪亮的(我们希望的)钻石:

            o
           / \
          o   \
         /     \
...--o--B       o   result
         \     /
          o   /
           \ /
            o

另请参阅Disable Git Rename Detectionthis answer

同时,事实证明至少有一些GUI界面将这一事实呈现给用户。当他们仅更改一个文件时,许多文件发生了很多变化,这让他们感到害怕。他们指示他们的GUI撤消所有其他更改 - 这意味着抛弃其他用户&#39;工作!他们然后提交这个,你必须恢复他们的合并以获得其他用户&#39;回来工作。

(&#34;触摸每个文件&#34;用户在其设置中启用行尾转换时的另一个来源。它们接收使用仅使用LF或CRLF结尾的传入代码,并将其转换为CRLF,或者只分别使用LF。然后他们提交所有这些更改,这意味着他们已经改变了每个文件的每一行。)