我从另一个分支拉出来时遇到了合并冲突并使用了#34;使用我的"而不是"使用他们的"错误地为一个文件。其他冲突的文件都得到了很好的照顾。
现在我想转移到此文件的旧状态(冲突前)并再次从分支中拉出以创建冲突并选择正确的选项。
我用过checkout git checkout" commit#" PathToFile 正确地将文件检索到它所处的状态(冲突前),但现在当我从其他分支拉出时,冲突就不存在了。
答案 0 :(得分:2)
只为一个文件执行此操作,而不使用完全花哨的Git"时间机器"版本控制的各个方面。但你也可以使用完整的花哨版本,因为它适用于更多的情况。
首先,请记住git pull
只是意味着:"运行git fetch
(带有一些参数),然后如果成功,运行git merge
(带一些其他参数),或者也许git rebase
(有一些论点)。"看起来你没有告诉它使用git rebase
而不是git merge
,所以我假设你想要并且正在使用合并行为。
请注意,您正在使用git merge
,要立即正确重复合并,您需要自己运行git merge
,而不是让git pull
执行此操作。所以你需要知道如何运行git merge
,以及它的作用。 "需要知道它的作用"即使您让git pull
运行git merge
",部分也是如此。要了解git merge
的作用,您还需要了解git fetch
的作用。我发现Git新手如果分别运行git fetch
和git merge
(或git rebase
),实际上会做得更好:它实际上需要一些神秘和混乱。
git fetch
做什么您的Git可以在您的存储库中运行,该存储库具有您的提交,由您的分支机构记住(指向)。你的Git还会与第二个Git交谈;其他Git拥有自己的存储库,具有自己的提交和分支。在不同的时间,你需要让你的Git调用他们的Git并带来他们的提交 - 特别是他们拥有的任何提交,你不会。
您可以通过运行git fetch
并为其指定远程的名称来执行此操作,可能是origin
。如果你只有一个遥控器 - 大多数人都这么做 - 它通常只叫origin
,而你的Git可以很容易地找出要取的是哪一个,因为那里只有一个人。但是,让我们把它写出来:
git fetch origin
这使用保存的URL-a" remote"保存一个URL - 调用另一个Git并获取其提交和分支并将它们带入您的存储库。它还重命名所有分支,以便他们的master
成为您的origin/master
,他们的develop
成为您的origin/develop
,依此类推。一旦你完成了他们的提交,他们就会变成你的#34;也是(不是"作者"或"提交者",但是你现在已经他们,在你的存储库中),所以现在你可以使用它们。
在我们进入合并步骤之前,通过某种方式可视化正在发生的事情非常重要。
如果你使用像gitk
或gitg
这样的图形用户界面或各种花哨的网页图形用户界面之一,那么很多图形图形会绘制图形。即使在命令行上,您也可以运行git log --graph --oneline --decorate --all
来获取提交的粗略图表。
对于StackOverflow,我更喜欢从左到右绘制提交,主要是水平提交。较新的提交是向右的。由于分支名称如master
指向分支的最尖端提交,我也将分支名称放在右侧,箭头指向分支的尖端:
o <- o <- o <-- master
每次提交,每个o
,指向&#34;向后&#34;到它以前的(父)提交。这些向后箭头主要是令人讨厌和分散注意力,所以让我们用连接线来绘制提交。并且,让我们绘制一个分支结构,在那里你有自己的提交,而你的git fetch
引入了其他一些提交。现在我们有了这个:
o--o--o <-- master
\
o--o <-- origin/master
同样,较新的提交是向右的,因此master
的提示是第一行的最后一次提交,origin/master
的提示是最后一次提交提交第二行提交。
每个提交都有一个&#34;真实姓名&#34;,这是一个很大的丑陋哈希ID,如17fac9e...
或ea6631f...
。为了能够更方便地谈论它们,请改为使用单字母名称或符号,并将上述内容重绘为:
o--*--A <-- master
\
o--B <-- origin/master
圆o
个节点是&#34;无聊的&#34;承诺,我们不需要再次提及。提交*
很特殊,因为master
和origin/master
加入,如果您从两者都向后工作。 (如果你向前工作,它就会分开.Git总是向后工作,所以如果你这样做,你会发现它更容易。)提交A
是特殊的,因为它是提示master
和提交B
是特殊的,因为它是origin/master
的提示。
我们还要记住,每个提交都有一个完整快照所有文件(由&#34;索引&#34;制作),这就是为什么你必须{{ 1}}提交之前文件到索引。
git add
做什么当你在git merge
并运行master
时,Git会发现两个提交:当前分支的提示和您命名的提示。这些是git merge origin/master
和A
。然后,Git找到了 merge base ,这是这两个分支重新组合在一起的第一个地方。那是我们的提交B
,这就是我们标记它的原因。
合并操作然后运行两个*
命令:
git diff
和
git diff <id-of-*> <id-of-A>
第一个差异代表&#34;我们改变了什么&#34;,第二个差异代表&#34;他们改变了什么&#34;。与所有git diff <id-of-*> <id-of-B>
一样,您必须询问:&#34;更改了与相关的内容?&#34;答案是:关于合并base-commit git diff
。
如果Git失败,它会让你完成合并,然后提交结果。
(合并都是使用索引和你的工作树进行的,但我们在这里大多可以忽略它。但是当Git让你完成合并时,这很重要。)
无论哪种方式,您都会获得新的提交,因此我们将其添加到图表中。新提交是一个合并提交,这意味着它有两个父项而不是一个:
*
请注意,旧的合并基础再次无聊,因此它不再有星号(o--o--A---M <-- master
\ /
o--B <-- origin/master
)。新的合并提交*
指向M
和 A
。这意味着提交B
现在位于分支B
上。它名为master
的远程跟踪分支上的仍。
你说你搞砸了合并origin/master
。你不会说你之后是否添加了更多的提交。如果你这样做,这一切都会变得更难,因为你可能想要保存这些提交。如果你推送这些提交(包括M
本身),它会变得更难,因为现在你可能 永远保存它们。但就目前而言,让我们看一下如果再次运行M
会发生什么。
我们开始时仍然有这个:
git pull
现在我们运行o--o--A---M <-- master
\ /
o--B <-- origin/master
。这会找到git fetch
上的新内容,这可能是什么都没有。因此它没有带来任何结果,并使origin
指向提交origin/master
。
现在我们运行B
,告诉它与我们的当前提交,git merge
合并origin/master
,即提交B
。这会找到M
和M
的合并基础,这是两个分支上的第一个提交。
第一次提交是......好吧,查看图表,然后向后关注B
。是M
吗?不,因为M
指向origin/master
,并且您不能向右移动,只能向左移动。出于同样的原因,它也不能B
。那么A
呢?这肯定在B
,它指向它。而且,啊哈,那也是origin/master
,因为master
指向M
!
所以合并基础是B
,如果Git做了合并,它会产生两个差异:
B
显然,从git diff B M # what we did
git diff B B # what they did
到B
没有区别,所以Git可以简单地跳过这一切:结合&#34;我们做了什么&#34; (更改B
以使B
)和&#34;他们做了什么&#34; (没有)给我们留下M
。所以M
什么都不做。
如果提取旧文件然后提交,会发生什么?好吧,现在你有了:
git merge
其中o--o--A---M--C <-- master
\ /
o--B <-- origin/master
具有从C
中提取的文件版本。现在我们运行A
。 Git找到git merge origin/master
和C
的合并基础。什么是合并基础?为什么,它只是再次B
。没有什么可以合并,Git什么都不做。
要让Git再次进行合并,必须使当前提交为commit B
。为此,您有以下几种选择:
A
的新分支名称。 (使用A
然后git branch <newname> <id-of-A>
或等效git checkout <newname>
。)git checkout -b <newname> <id-of-A>
放弃提交git reset
和M
,以便C
指向master
。 (使用A
,确保您没有未保存的工作。)git reset --hard
提交本身,获得一个&#34;分离的HEAD&#34;,其中git checkout
指向HEAD
。让我们绘制中间选项,因为它可能是你想要的特殊情况。我不能灰掉&#34;文字,因此我会重新绘制图表,只需将A
和M
标记为C
:
abandoned
由于提交o--o------A <-- master
\ \
\ M--C [abandoned]
\ /
o--B <-- origin/master
不再有指向它的名称,因此您再也看不到它了。由于C
是让我们找到C
的唯一途径,因此您再也看不到M
了。我们仍然可以找到名称为M
的提交A
和名称为master
的{{1}},所以让我们更简单地重新绘制为:
B
看看那个,我们回到以前的状态!现在我们可以运行origin/master
来重新进行合并。 o--o--A <-- master
\
o--B <-- origin/master
和git merge
的合并基础是A
(我标记为B
的左侧的提交 - 可能是时候放置A
了} 背部)。 Git将重新尝试合并,并以相同的方式失败,同样的合并冲突。
如果您推送了提交*
以及任何后续提交,或以其他方式将其提交给其他人,那么您将为他们做更多的工作&#34;撤回&#34;你的合并。在这种情况下,请考虑这是否正常。如果是,你仍然可以做到。如果没有,请在那里留下*
(以及您应该保留的任何后续提交)。
在这种情况下,您可以使用&#34;分离的HEAD&#34;方法,或创建指向M
的新分支:
M
现在您可以A
或o--o------A <-- HEAD
\ \
\ M--C <-- master
\ /
o--B <-- origin/master
重试合并提交,并且这次正确解析它。进行新的提交,它将被添加到git merge origin/master
,就好像它是一个常规分支(它主要是,它只是一个没有名字!):
git merge <id-of-B>
(图形变得混乱,现在没有很好的方法可以绘制它。)这个新的合并HEAD
中包含正确合并的文件,因为这次你做得很好。
现在您可以再次o--o------A-----M2 <-- HEAD
\ \ /
\ M-/-C <-- master
\ /_/
o--B <-- origin/master
并简单地从合并M2
中获取正确的文件(您的ID可以保存,剪切和粘贴,或者您可以为此分配名称一会儿,然后删除那个分支)。