我有一个GitHub存储库,其中的提交显示在日志中,并在GitHub Desktop应用程序中可见,但是提交中的更改未应用于代码(或在Visual Studio中可见)。
有问题的提交是一个月前发生的分支合并的一部分。我们将master合并到分支中,然后将分支合并回master中。 “将'主'合并到'分支'”提交显示了合并中的所有更改。其中一些更改未应用到代码中,Visual Studio表示受影响的页面自去年初以来就没有更新过。这与合并回master之后删除分支有关吗?
我们在合并后检查了所有提交,但这些提交都没有更改受此问题影响的页面上的任何代码。页面没有重命名,删除或移动。
我们试图还原合并以查看会发生什么,并且大多数提交在GitHub Desktop中消失了(从58个更改的文件更改为8个文件),仅对合并应用了一些更改。我们还尝试从此合并中挑选提交,并正确应用它们。由于我们可以这样做来解决问题,因此我们对为什么未将提交应用于相关代码文件更感兴趣。
更新:
我们尝试在合并提交之后对提交执行git checkout <sha1>
,发现代码是不同的(以后3次提交-也是合并),但是这些提交中没有对代码文件的引用。
答案 0 :(得分:1)
值得注意的是,提交不是,也不包含更改。提交包含完整快照。提交查看者故意撒谎(您通常希望这样做),以便显示一组更改。< / p>
这背后的机制很重要。提交是快照(例如,天气预报告诉您当前的温度是20°C),并且您想知道它现在的差异。您需要为何时选择另一个提交,例如“昨天”。那么如果昨天的温度为19˚C,今天的温度为20˚C,则差异就是1 itC的温度。但是要获得该结果,您必须选择前一天进行比较。
Git中的每个提交均由其哈希ID唯一标识。哈希ID是Git如何获取快照中的所有文件以及有关提交的元数据,例如谁进行创建,何时创建以及为什么生成(日志消息)。每次提交中保存的一项内容是上一个提交的哈希ID。这就是Git提交查看器用来构造差异的地方。
提交查看器不会显示带有哈希 H 的提交中每个文件的每一行。取而代之的是,它将找到提交的前身-用Git的说法是 parent 。那个父母有一些不同的哈希 G 。查看器提取两个提交,并进行比较,然后告诉您:在G和H之间,这些文件是不同的,并且对这些行进行了更改。通常要短得多,而且比有用得多-这是H中的完整快照。
但这在合并时会分解。如果我们绘制一组不错的线性提交:
... <-F <-G <-H <-- you-are-here
(箭头指向后,因为每个提交记录了其父对象;父母不记得自己的孩子),比较G
与H
很容易。但最终您将两条开发线结合在一起:
o--...--o--K
/ \
...--* M <-- mainline
\ /
o--o--...--L <-- branch
主线在某个点分开,形成了两个不同的人或团体。然后,我们(或无论如何)使用了git checkout mainline; git merge branch
,并经历了 merge 操作的整个可怕的 1 和神奇的 2 过程,从而导致此合并提交 M
。
提交M
与其他任何提交一样,都具有快照和一些元数据。快照与其他任何快照一样。 M
唯一的特别之处在于,在其元数据中,它不仅将提交K
列为其父项。而是将两者提交-K
和 L
-列为其两个父母。
1 这实际上并不可怕。
2 这也不是魔术。见下文。
让我们快速了解git merge
和合并冲突。如果没有冲突,Git会自己进行整个合并。通常,这些情况不会引起这种困惑,所以让我们看看发生冲突时会发生什么。
要开始合并,Git只需将*
与K
进行比较(就像Git总是比较任何简单的提交对一样),以找出不同之处。然后,Git比较*
-vs-L
,以找出不同之处。然后,Git 将两组更改组合在一起。这是合并的 或我想称做的动词 的一部分。合并的更改将应用于提交*
中的快照。
请记住,每个提交都包含一个快照。提交*
时,所有文件的状态都与创建*
时的状态相同。提交K
的所有文件都处于其他状态,而提交L
的所有文件都处于第三状态。 K
中甚至可能有*
中没有的文件,和/或L
中没有的*
中有文件,依此类推,但是通常大多数文件大部分都在这三个输入中。
假设“我们”是指在K
行中工作的人,而“他们”是指在L
行中工作的人。我们更改了文件A,B和C。更改了文件B,C和D。然后Git仅将我们对A的所有更改以及对D的所有更改。这很容易,因为我们没有碰触D和他们没有碰到A。合并的那部分完成了。
现在,Git找出了我们在文件B中更改了的几行 ,以及同一文件中它们的哪些行已更改。如果我们的行根本不重叠(请注意,Git有时会将“触摸”视为重叠),那么Git可以将提交*
的两者更改应用于文件B。合并的那部分现在也已完成。
Git找出我们在C语言中更改了哪些行,以及它们更改了哪些行。嗯,这次我们都更改了 same 行。 Git用冲突标记将更改的组合写入工作树,并声明合并冲突。
由于合并 发生冲突,因此Git停止并从正在执行合并的人那里获得帮助。解决问题是他们的工作。有很多修复方法,但都以相同的方式结束:进行修复的人将C
文件的正确版本写入工作树并运行{{ 1}}告诉Git:这是正确的结果。
Git不会检查他们写的内容,只会将他们放入最终文件中的内容带走。如果他们完全搞砸了所有事情,例如完全丢掉了您的代码,那么Git就可以了! Git假设他们知道他们在做什么。
它们现在运行git add C
或git commit
,Git使用完成的合并快照进行合并提交git merge --continue
,看起来就像我们绘制了它。
因此,让我们回到提交查看器。您要求它查看提交M
。它像往常一样显示元数据-进行提交的人的名字,依此类推。根据查看者的不同,它可能不会显示两个父哈希ID。它可能会向您显示一条日志消息,表明运行M
的人员曾经用来记录为什么进行合并并保存任何重要注释。如果此人非常勤奋,则该日志消息甚至可能很有用……但是,大多数人使用自动生成的,几乎毫无价值的日志消息:“合并分支...”。
现在,您的查看器应该继续显示您要在此提交中进行更改。但是现在有一个问题。要显示发生了什么更改,查看者必须查看父提交并进行比较。没有一个父母。有两个父母。观众会使用哪一个?
此处的实际答案取决于查看者。有些观看者只是完全放弃而没有向您显示什么。例如,git merge
就是这样做的。听起来您可能正在使用这种查看器。 git log -p
运行的另一个查看器试图发挥作用:它实际上将合并git show
与两个父母,M
和 K
。但是,a,这位观众试图帮忙。它与合并可能存在合并冲突的地方有关,因此如何不显示任何L
中的文件与完全匹配的文件 {{1}中的一个或M
中的一个。
如果进行合并的人通过扔掉 不正确对它进行了错误,则 应该包含在{{1}中},这种查看器也将丢弃这些更改。在这种情况下,文件C与提交K
中的其副本完全匹配。因此L
,作为合并查看器,不会向您显示文件C 。
(当然,使用M
作为提交查看器会更糟:即使这四个文件有所更改,它也不会显示A,B,C或D中的任何一个。 )
您可以指示 L
(和git show
)分解合并提交成两个虚拟提交。也就是说,给定:
git log
您可以让他们假装他们拥有:
git log
先显示git show
-vs-...--K
\
M
/
...--L
,然后显示...--K--M1
...--L--M2
-vs-K
。对于这些情况,这通常很有用。为此,请将M1
添加到L
或M2
中。 (请注意,-m
和git log
永远不会进入存储库,它们只是在查看合并提交的“显示差异”部分期间假装提交。)
如果某人制作了错误的合并快照,那么许多查看者将不会向您显示。找到它的方法是查看合并前后的提交。如果有人保持的操作,则需要教他们如何正确合并。 很少扔掉他们的更改并改用我的是正确的。 Git提供了此选项,但他们应该谨慎使用该选项,不仅仅是因为它解决了他们的冲突。