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