我使用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交互。
1条答案
按热度按时间mlmc2os51#
有没有可能我不小心把git设置成在有冲突或其他情况时忽略传入的文件?
从技术上讲,是的,通过
.gitattributes
。实际上,这是非常不可能的。请参阅下面更长的解释。注意:我从来没有能够让git终端命令工作(git命令不被识别),并且不确定如何安装以及在哪里输入它们。
我避开Windows,但我相信大多数人都是从Git-for-Windows发行版安装Git-for-Windows,比如here。同一个网站上有Scott Chacon的Pro Git书籍,可以免费获得。
剩下的部分是关于Git的合并; VS有自己的前端,甚至可能不使用Git的合并。
Git如何执行合并,作为高级概述
首先,你需要知道Git最终是关于 commits(不是文件,不是分支,而是提交),每个提交包含两件事:
每个提交都有一个唯一的、随机的(虽然实际上不是随机的)数字,以hexadecimal编码,例如
63bba4fdd86d80ef061c449daa97a981a9be0792
。Git正式称之为 * 对象ID* Git需要这个数字来找到一个提交,但显然这些数字对人类来说非常糟糕,所以Git允许我们使用分支或标记名。我们将跳过所有关于它们如何工作的细节,只注意数字是存在的,并且任何一个特定提交中的 * 元数据 * 都包含了 * 上一个 * 提交的 * 数字 *。这意味着,给定某个分支中 latest 提交的哈希ID,Git可以很容易地找到该分支中较早的提交。如图所示,较新的提交在右边,我们可以想象从某个常见的提交序列分叉出两个分支,以
H
为结束,如下所示:在这里,name
br1
定位到(对于us和Git)分支br1
上的 latest commit,即commitJ
。CommitJ
的元数据包括较早提交I
的原始散列ID。CommitJ
还具有每个文件在提交J
时的形式的完整存档。CommitI
,作为提交,具有每个文件的完全存档(当然是在提交I
时的形式)和一些元数据,并且提交I
中的元数据经由所保存的散列ID来定位提交H
。类似地,名称
br2
会找到L
,L
会找到K
,K
会找到H
。当你在任何一个分支上运行
git merge
时,Git会使用这个分支的名字找到 other 分支的最新提交,然后使用 graph,加上最低公共祖先算法,找到最佳的 shared 提交,在本例中为提交H
。**我们将提交H
称为 * 合并基 *。**该合并基是合并实际工作方式。请记住,合并的目标是合并工作,但每个提交只有一个 * 快照 *。任何一个给定的提交,本身并没有“工作”,它只有一个快照。提交中的“工作完成”是隐含的:使用提交和它的元数据,Git将备份到 * 上一次 * 提交,并 * 比较 * 两个快照。2“完成的工作”是这两个快照之间的 * 差异 *。
这里,需要组合的工作是在
br1
上的 * 两个 * 提交I
和J
中完成的任何工作-这是“在br1
上完成的工作,”与在br2
上的 * 两个 * 提交K
和L
中完成的任何工作。Git可以获得一个共同的起点,去寻找“完成的工作”。具体来说,我们运行:
(to在提交
J
时“开启”br1
),然后:(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的特殊之处在于,它的元数据中列出了两个父提交(技术上是“两个或更多”,但我们不会在这里讨论“更多”的情况),而不是一个父提交。
提交
M
中的 snapshot 是由工作组合生成的。元数据是除了这两个父元数据之外的所有常用标准元数据。一些特殊情况
现在,有两个退化的情况涉及到 commit graph。上面,我们画了一个合并基础提交之后是 both 分支上的提交的情况。(作为练习的附带问题:哪些分支保存
H
及更早的提交?)假设我们有:
如果我们像以前一样使用
git switch br1 && git merge br2
,我们要求Git将我们做的工作与他们做的工作合并,但是合并的基础是commitH
,“我们的”提交也是H
,“他们的”提交是J
,所以我们所做的工作自动地是“什么都不是”。H
中的快照与H
中的快照之间的差异是 * 完全没有差异 *。显然,将“某物”与“无”结合会产生“某物”。真正合并的最终结果将看起来像这样:
M
中的 * 快照 * 与J
中的快照完全匹配。**你可以 * 强迫 * Git使用
git merge --no-ff
进行“真正的合并”。**但如果你不这么做--大多数人都不这么做--Git会使用一个比平常更快的快捷方式,让 namebr1
选择与namebr2
* 相同的提交 *:根本就没有新的提交
M
!Git称之为 * 快进合并 *,尽管实际上并没有合并。(Git文档的其他部分有时会称之为 * 快进 * 或 * 快进操作 *,省略了“合并”一词。Git从来不注重自我一致性......)您只剩下工作树中来自commitJ
的文件,就好像你运行了git switch br2
,但是你仍然“在”br1
上,并且名字br1
现在选择提交J
。现在请考虑此图:
我们切换到
br1
,让Git合并br2
。这里最好的共享提交是像以前一样提交H
,但是提交H
* 已经在分支br1
中了 *,因为分支中的提交集是通过从末尾开始并永远向后工作来确定的(或者直到我们第一次提交)。所以br2
的提示提交已经包含在内了。实际上没有什么可做的,Git会这么说,什么也不做。(This也为您提供了上述练习的答案:提交
H
在 * 两个分支上 *,在我们的例子中Git必须进行真实的的合并。困难案例:“两面”都有变化
当Git进行真正的合并时:
而有些文件的“两边”都有改动,Git必须做真实的的工作来合并这些改动。Git的内建规则很简单也很愚蠢:
J
和从合并基到L
的diff逐行显示删除和添加了哪些行;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
实际上并不起作用。这就是人们不这么做的 * 另一个 * 原因:它不起作用。21有人需要编写一个 * 好的 * XML合并驱动程序。这本身就是一个相当困难的问题,不管是否要将其连接到Git。然后,该XML驱动程序应该作为一个标准的内置可选合并驱动程序添加到Git中。这将是一个巨大的服务,所有那些有XML文件存储在Git中的人。像Git那样逐行合并XML文件,往往会破坏它们。
2Git需要一种方法来让它工作,虽然很难看,但在合并驱动程序或gitattributes条目中添加一个简单的注解,将驱动程序标记为"required"就足够了。