git在冲突之前返回到文件的状态

时间:2016-11-17 05:25:18

标签: git bitbucket conflict git-checkout

我从另一个分支拉出来时遇到了合并冲突并使用了#34;使用我的"而不是"使用他们的"错误地为一个文件。其他冲突的文件都得到了很好的照顾。

现在我想转移到此文件的旧状态(冲突前)并再次从分支中拉出以创建冲突并选择正确的选项。

我用过checkout git checkout" commit#" PathToFile 正确地将文件检索到它所处的状态(冲突前),但现在当我从其他分支拉出时,冲突就不存在了。

1 个答案:

答案 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 fetchgit 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;也是(不是"作者"或"提交者",但是你现在已经他们,在你的存储库中),所以现在你可以使用它们。

绘制提交图

在我们进入合并步骤之前,通过某种方式可视化正在发生的事情非常重要。

如果你使用像gitkgitg这样的图形用户界面或各种花哨的网页图形用户界面之一,那么很多图形图形会绘制图形。即使在命令行上,您也可以运行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;承诺,我们不需要再次提及。提交*很特殊,因为masterorigin/master加入,如果您从两者都向后工作。 (如果你向前工作,它就会分开.Git总是向后工作,所以如果你这样做,你会发现它更容易。)提交A是特殊的,因为它是提示master和提交B是特殊的,因为它是origin/master的提示。

我们还要记住,每个提交都有一个完整快照所有文件(由&#34;索引&#34;制作),这就是为什么你必须{{ 1}}提交之前文件到索引。

git add做什么

当你在git merge并运行master时,Git会发现两个提交:当前分支的提示和您命名的提示。这些是git merge origin/masterA。然后,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尝试合并这两组更改。如果它自己成功,它就会认为它一切都是正确的,这并不总是正确的,但经常令人惊讶的是:&#34;令人惊讶的是#34;因为Git不知道代码,它只使用简单的文本规则。然后提交结果。

如果Git失败,它会让你完成合并,然后提交结果。

(合并都是使用索引和你的工作树进行的,但我们在这里大多可以忽略它。但是当Git让你完成合并时,这很重要。)

无论哪种方式,您都会获得新的提交,因此我们将其添加到图表中。新提交是一个合并提交,这意味着它有两个父项而不是一个:

*

请注意,旧的合并基础再次无聊,因此它不再有星号(o--o--A---M <-- master \ / o--B <-- origin/master )。新的合并提交*指向M A。这意味着提交B现在位于分支B上。它名为master的远程跟踪分支上的

你说你搞砸了合并origin/master。你不会说你之后是否添加了更多的提交。如果你这样做,这一切都会变得更难,因为你可能想要保存这些提交。如果你推送这些提交(包括M本身),它会变得更难,因为现在你可能 永远保存它们。但就目前而言,让我们看一下如果再次运行M会发生什么。

git pull = git fetch&amp;&amp; git merge

我们开始时仍然有这个:

git pull

现在我们运行o--o--A---M <-- master \ / o--B <-- origin/master 。这会找到git fetch上的新内容,这可能是什么都没有。因此它没有带来任何结果,并使origin指向提交origin/master

现在我们运行B,告诉它与我们的当前提交git merge合并origin/master,即提交B。这会找到MM合并基础,这是两个分支上的第一个提交。

第一次提交是......好吧,查看图表,然后向后关注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/masterC的合并基础。什么是合并基础?为什么,它只是再次B。没有什么可以合并,Git什么都不做。

你如何让Git重新合并

要让Git再次进行合并,必须使当前提交为commit B。为此,您有以下几种选择:

  • 您可以创建一个指向提交A的新分支名称。 (使用A然后git branch <newname> <id-of-A>或等效git checkout <newname>。)
  • 您可以使用git checkout -b <newname> <id-of-A> 放弃提交git resetM,以便C指向master。 (使用A,确保您没有未保存的工作。)
  • 你可以git reset --hard提交本身,获得一个&#34;分离的HEAD&#34;,其中git checkout指向HEAD

让我们绘制中间选项,因为它可能是你想要的特殊情况。我不能灰掉&#34;文字,因此我会重新绘制图表,只需将AM标记为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

现在您可以Ao--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可以保存,剪切和粘贴,或者您可以为此分配名称一会儿,然后删除那个分支)。