Visual Studio/git的“合并到当前分支”未合并我的更改

jjhzyzn0  于 2022-11-27  发布在  Git
关注(0)|答案(1)|浏览(403)

我使用git进行源代码管理,当我使用Visual Studio 2019的“Merge into current分支”函数时,其他分支中的更改没有完全应用到当前分支。
举个例子:我在master分支中将一个变量从“EmphasicAdjective”重命名为“StrongAdjective”。然后我切换到一个原型分支,右键单击“master”,选择“合并到当前分支”。
没有报告合并冲突,并且我可以在原型分支的历史记录中看到发生更改的提交。但是,在合并后,我立即看到一系列“The name 'EmphasicAdjective' does not exist in the current context”错误。变量定义被正确重命名,但各种辅助文件没有得到更改。
有没有可能我不小心设置了git,当有冲突或者其他什么的时候忽略传入的文件?我应该在哪里检查?
注意:我从来没有使用过git终端命令(无法识别git命令),也不确定如何安装以及在哪里输入。我通常通过Visual Studio与git交互。

mlmc2os5

mlmc2os51#

有没有可能我不小心把git设置成在有冲突或其他情况时忽略传入的文件?
从技术上讲,是的,通过.gitattributes。实际上,这是非常不可能的。请参阅下面更长的解释。
注意:我从来没有能够让git终端命令工作(git命令不被识别),并且不确定如何安装以及在哪里输入它们。
我避开Windows,但我相信大多数人都是从Git-for-Windows发行版安装Git-for-Windows,比如here。同一个网站上有Scott Chacon的Pro Git书籍,可以免费获得。
剩下的部分是关于Git的合并; VS有自己的前端,甚至可能不使用Git的合并。

Git如何执行合并,作为高级概述

首先,你需要知道Git最终是关于 commits(不是文件,不是分支,而是提交),每个提交包含两件事:

  • 每个文件的完整快照,一直冻结,有点像WinRAR存档,除了Git巧妙地节省了大量的空间(相对于每次提交都为每个文件制作一个存档);和
  • 一些 * 元数据 *,包括此提交的父提交的原始哈希ID。

每个提交都有一个唯一的、随机的(虽然实际上不是随机的)数字,以hexadecimal编码,例如63bba4fdd86d80ef061c449daa97a981a9be0792。Git正式称之为 * 对象ID* Git需要这个数字来找到一个提交,但显然这些数字对人类来说非常糟糕,所以Git允许我们使用分支或标记名。我们将跳过所有关于它们如何工作的细节,只注意数字是存在的,并且任何一个特定提交中的 * 元数据 * 都包含了 * 上一个 * 提交的 * 数字 *。
这意味着,给定某个分支中 latest 提交的哈希ID,Git可以很容易地找到该分支中较早的提交。如图所示,较新的提交在右边,我们可以想象从某个常见的提交序列分叉出两个分支,以H为结束,如下所示:

I--J   <-- br1
         /
...--G--H
         \
          K--L   <-- br2

在这里,namebr1定位到(对于us和Git)分支br1上的 latest commit,即commit J。Commit J的元数据包括较早提交I的原始散列ID。Commit J还具有每个文件在提交J时的形式的完整存档。Commit I,作为提交,具有每个文件的完全存档(当然是在提交I时的形式)和一些元数据,并且提交I中的元数据经由所保存的散列ID来定位提交H
类似地,名称br2会找到LL会找到KK会找到H
当你在任何一个分支上运行git merge时,Git会使用这个分支的名字找到 other 分支的最新提交,然后使用 graph,加上最低公共祖先算法,找到最佳的 shared 提交,在本例中为提交H。**我们将提交H称为 * 合并基 *。**该合并基是合并实际工作方式。
请记住,合并的目标是合并工作,但每个提交只有一个 * 快照 *。任何一个给定的提交,本身并没有“工作”,它只有一个快照。提交中的“工作完成”是隐含的:使用提交和它的元数据,Git将备份到 * 上一次 * 提交,并 * 比较 * 两个快照。2“完成的工作”是这两个快照之间的 * 差异 *。
这里,需要组合的工作是在br1上的 * 两个 * 提交IJ中完成的任何工作-这是“在br1上完成的工作,”与在br2上的 * 两个 * 提交KL中完成的任何工作。Git可以获得一个共同的起点,去寻找“完成的工作”。
具体来说,我们运行:

git switch br1

(to在提交J时“开启”br1),然后:

git merge br2

(to找到H后,Git现在为所有三次提交中的每个文件都有该文件的三个版本:

  • 来自提交H合并基础版本;
  • 提交J中的“我们的”或HEAD版本;和
  • 提交L中的“他们的”版本。

忽略更复杂的新文件、删除文件或重命名文件的情况,让我们只考虑在三次提交中有人修改了一些文件的情况:

  • 有可能 nobody 根本没有修改过文件。在这种情况下,所有三个版本完全匹配,Git可以使用其中任何一个版本。

  • 或者,也许“我们”将文件从H更改为J,但他们根本没有修改该文件,那么Git应该使用 * 我们的 * 版本,这就是Git所做的。

  • 也许“他们”把文件从H改成了L,但我们根本没有碰这个文件,那么Git应该使用 * 他们的 * 版本,这就是Git所做的。

  • 最后一种可能性是,我们和他们都修改了文件。这是Git必须做真实的合并工作的地方。这是 * 唯一 * Git费心做真正合并工作的情况,这对.gitattributes和下面的合并驱动程序部分很重要。

对于上面提到的所有简单的情况,Git只需要从文件的三个副本中取 * 一个 *-无论哪个是正确的(如果它们都是正确的,你就无法分辨Git取了哪个,所以这无关紧要)。
假设一切顺利,Git现在将创建一个新的 merge commit。merge commit的特殊之处在于,它的元数据中列出了两个父提交(技术上是“两个或更多”,但我们不会在这里讨论“更多”的情况),而不是一个父提交。

I--J
         /    \₁
...--G--H      M   <-- br1
         \    /²
          K--L   <-- br2

提交M中的 snapshot 是由工作组合生成的。元数据是除了这两个父元数据之外的所有常用标准元数据。

一些特殊情况

现在,有两个退化的情况涉及到 commit graph。上面,我们画了一个合并基础提交之后是 both 分支上的提交的情况。(作为练习的附带问题:哪些分支保存H及更早的提交?)
假设我们有:

...--G--H   <-- br1
         \
          I--J   <-- br2

如果我们像以前一样使用git switch br1 && git merge br2,我们要求Git将我们做的工作与他们做的工作合并,但是合并的基础是commit H,“我们的”提交也是H,“他们的”提交是J,所以我们所做的工作自动地是“什么都不是”。H中的快照与H中的快照之间的差异是 * 完全没有差异 *。
显然,将“某物”与“无”结合会产生“某物”。真正合并的最终结果将看起来像这样:

...--G--H-----¹M   <-- br1
         \    /²
          I--J   <-- br2

M中的 * 快照 * 与J中的快照完全匹配。

**你可以 * 强迫 * Git使用git merge --no-ff进行“真正的合并”。**但如果你不这么做--大多数人都不这么做--Git会使用一个比平常更快的快捷方式,让 namebr1选择与name br2 * 相同的提交 *:

...--G--H--I--J   <-- br1, br2

根本就没有新的提交M!Git称之为 * 快进合并 *,尽管实际上并没有合并。(Git文档的其他部分有时会称之为 * 快进 * 或 * 快进操作 *,省略了“合并”一词。Git从来不注重自我一致性......)您只剩下工作树中来自commit J的文件,就好像你运行了git switch br2,但是你仍然“在”br1上,并且名字br1现在选择提交J
现在请考虑此图:

I--J   <-- br1
         /
...--G--H   <-- br2

我们切换到br1,让Git合并br2。这里最好的共享提交是像以前一样提交H,但是提交H * 已经在分支br1中了 *,因为分支中的提交集是通过从末尾开始并永远向后工作来确定的(或者直到我们第一次提交)。所以br2的提示提交已经包含在内了。实际上没有什么可做的,Git会这么说,什么也不做。
(This也为您提供了上述练习的答案:提交H在 * 两个分支上 *,在我们的例子中Git必须进行真实的的合并。

困难案例:“两面”都有变化

当Git进行真正的合并时:

I--J   <-- br1
         /
...--G--H
         \
          K--L   <-- br2

而有些文件的“两边”都有改动,Git必须做真实的的工作来合并这些改动。Git的内建规则很简单也很愚蠢:

  • 从合并基到J和从合并基到L的diff逐行显示删除和添加了哪些行;
  • 如果在“一侧”删除和添加行的区域与在“另一侧”删除和添加行的区域不重叠或相邻,Git接受这些更改;
  • 如果区域显示两个差异触及了合并基础(H commit)文件中的 * 相同 * 行,Git将声明 * 合并冲突 *。

当Git遇到合并冲突时,Git会让你这个人类程序员来解决这个问题。你应该用你喜欢的方式来解决这个问题--可能使用所有三个输入文件,也可能使用Git在合并文件时的混乱尝试,并加上冲突标记--一旦你得到了文件的“正确”版本,你就告诉Git:* 这是正确的版本 *,也是Git为最终合并结果存储的版本。

*Git相信你, 不管你在这个文件里放了什么 *。**所以如果你做错了,Git相信你做对了,并使用它。对于这个合并冲突的情况,整个结果取决于你。

现在,这里的主要问题是Git是简单和愚蠢的。它是逐行工作的,**即使这没有意义。**因此,你可以为特定的文件名提供一个 * 合并驱动程序 *(xyz.blah)或模式(*.xml).**为了提供您自己的、可能更智能的合并驱动程序,您可以在.gitattributes文件或类似文件中列出文件名或模式,使用merge=*name*。*然后,您还必须通过定义此处使用的 * name,在.git/config.gitconfig文件中定义合并驱动程序本身。
这是相当复杂的。很容易出错,很难做对,而且很难编写一个好的合并驱动程序。有一种合并驱动程序并不难写,那就是"只保留一个输入"驱动程序; "保留我们的"是最简单的,因为技术上的原因,我们不会在这里讨论。但是因为太难了,大多数人根本不会这么做。1
尽管如此,这还是你的问题的答案:
你有没有 * 设置Git忽略传入的文件?是的,使用"保留我们的"合并驱动程序。请注意,这种驱动程序实际上并不工作,无论如何,因为如果你 * 没有 * 更改一个文件,而他们 * 确实 * 更改了一个文件,Git会采用他们的版本,
完全不用停下来运行合并驱动程序 *。所以人们尝试的merge=ours实际上并不起作用。这就是人们不这么做的 * 另一个 * 原因:它不起作用。2
1有人需要编写一个 * 好的 * XML合并驱动程序。这本身就是一个相当困难的问题,不管是否要将其连接到Git。然后,该XML驱动程序应该作为一个标准的内置可选合并驱动程序添加到Git中。这将是一个巨大的服务,所有那些有XML文件存储在Git中的人。像Git那样逐行合并XML文件,往往会破坏它们。
2Git需要一种方法来让它工作,虽然很难看,但在合并驱动程序或gitattributes条目中添加一个简单的注解,将驱动程序标记为"required"就足够了。

相关问题