Git还原和樱桃选择

时间:2017-02-03 18:31:06

标签: git git-revert cherry-pick

我对某事感到困惑,这是一个总结。

我们的仓库有一个主分支和一个d2l_phase_4分支。自从创建了d2l_phase_4分支以来,已经为master添加了几个新的提交。

今天有人错误地将拉取请求合并到了应该定位到d2l_phase_4分支的提交的主人。

因此我这样做了:

  1. 恢复提交并推送到fork / master
  2. 向主回购/主人提出拉动请求。
  3. 合并拉请求。
  4. 此时我们已经安全地撤消了对我们主分支的提交效果。

    然后我这样做了:

    1. 检出d2l_phase_4分支
    2. Cherry选择了master的原始提交。
    3. 推到fork / d2l_phase_4
    4. 针对主要repo / d2l_phase_4
    5. 发出拉取请求
    6. 合并此拉取请求
    7. 此时d2l_phase_4分支与原始提交有所需的更改,到目前为止一直很好。

      最后,我想让d2l_phase_4分支与master上的所有其他东西保持同步,因为d2l_phase_4分支已经创建。所以再次在d2l_phase_4分支上我从master合并并创建了一个新的合并提交。

      然后我将这个更新的d2l_phase_4分支推送到我的fork / d2l_phase_4,最后创建一个针对主repo / d2l_phase_4的拉取请求,然后合并该PR。

      但是 - 我突然意识到Id现在已经在早期版本中从主分支合并,因此必须使我之前提到的提交无效!

      然而,当检查d2l_phase_4上的文件状态时,没有从master中合并的恢复的痕迹 - 这正是我想要的 - 但我不明白。

      为什么我的樱桃选择了对d2l_phase_4的提交没有被从master合并的revert无效?我预计我需要在d2l_phase_4分支上恢复我的恢复,但是当我尝试这个git似乎告诉我没有什么可以恢复...

      如果我查看所涉及的文件之一,在d2l_phase_4分支上,那么我看不到恢复或任何事情的痕迹,我只能看到樱桃选择了我做的提交。

1 个答案:

答案 0 :(得分:1)

这有点复杂,并且 - 通常 - 要理解发生了什么,我们必须绘制(部分)提交图。

这是您在错误合并之前显然(基于您的文字)的内容。我可能有错误的提交数量,但整体图表必须合理地接近:

...--A--B--C--D--E   <-- master
         \
          F--G--H    <-- dl2_phase_4

对于下一部分,我们必须猜测,因为只有(以及有权访问你的回购的其他人)拥有正确的信息:

  

今天有人错误地将拉取请求合并到了应该定位到d2l_phase_4分支的提交的主人。

&#34;定位&#34;不重要;重要的是提交图。拉取请求只是意味着有人发布了一个与您拥有相同提交的存储库,以及至少一次提交。关键问题是,其中提交在他们的中,我不知道答案。但是,我们只是说它出现在D之后,所以它看起来像这样:

                __I   <-- their-commit
               /
...--A--B--C--D--E    <-- master
         \
          F--G--H     <-- dl2_phase_4

(如果它出现在E之后,我们可以有一个快进而不是合并,但最终这没有区别。如果它出现在F之后,图表会变得混乱,尽管它最终没有区别。)

然后,就像你说的那样,将其合并到master,提供一个新的合并提交(我称之为M,但我们会进行更多的合并,所以让我们继续使用单个字母并将其称为J):

                __I
               /   \
...--A--B--C--D--E--J   <-- master
         \
          F--G--H    <-- dl2_phase_4

(我们不再需要标记I,因为它只是J的第二个父级 - 第一个父级是E;第一个是I vs第二个父母的区别往往会在这些图纸中迷失。)

然后,根据您的三个步骤,使用一个冗长的复杂过程(推送到fork,从fork中撤回)恢复合并,这只会提供与简单地撤消提交K相同的效果master上的新提交 __I / \ ...--A--B--C--D--E--J--K <-- master \ F--G--H <-- dl2_phase_4

K

关于提交E的重要注意事项是它的 - 存储的快照 - 提交的树完全相同J提交E只是IK的合并(因此J = E + I) 1 和{ {1}}&#34;否定我&#34;添加,因此K = E + I - I = E。

1 这不保证!如果I重复E中的更改,则J实际上不是E加我,它是E加我减去重复的部分。但如果是这种情况,我们会在稍后看到效果,因此I不得在E中复制某些内容,或者在I之后复制E。这就是为什么实际拥有图表,有时候存储库本身很重要的原因。

然后,您经历了另一个长期错综复杂的过程,实际上只相当于一个简单的git cherry-pick

  
      
  1. Cherry选择了master的原始提交[into dl2_phase_4]。
  2.   

目前尚不清楚哪一个是&#34;原作&#34; (IJ,但如果I实际上是快进的,则没有J),但这也没关系:我们只是得到一份副本,我就是将致电I'

                __I
               /   \
...--A--B--C--D--E--J--K   <-- master
         \
          F--G--H--I'      <-- dl2_phase_4

虽然I'I不同,但两个相对更改 - 如果我们运行git diff <id-of-D> <id-of-I>git diff <id-of-H> <id-of-I'>,我们会得到什么。这是git cherry-pick的作用:它对其父项进行了一些提交,然后将生成的diff应用于当前提交并进行新的提交。

  

最后,我想让d2l_phase_4分支与master上的所有其他东西保持同步,因为d2l_phase_4分支已经创建。所以再次在d2l_phase_4分支上我从master合并并创建了一个新的合并提交。

所以,让我们画 ,现在:

                __I
               /   \
...--A--B--C--D--E--J--K     <-- master
         \              \
          F--G--H--I'----L   <-- dl2_phase_4
  

然而 - 我突然意识到我现在已经在早期的主分支恢复中合并了...... [但是]检查d2l_phase_4上的文件状态那里没有恢复的痕迹从主人那里合并 - 这正是我想要的 - 但我不明白。

这将进入第一个关键项目:

Git merge并没有关注任何&#34;内部&#34;提交

  

为什么我的樱桃选择了对d2l_phase_4的提交没有被从master合并的revert无效? [剪断]

正如您从图表中看到的那样,我们正在合并提交K,同时&#34; on&#34;提交I'合并基础 - 图表行重新加入的提交提交 - 提交B

合并进程,merge-as-a-verb,实质上是在执行(然后组合)两个git diff s:一个从合并库到当前提交,一个从合并基地到另一个提交。因此:

git diff <id-of-B> <id-of-I'>
git diff <id-of-B> <id-of-K>

我们已经注意到,就 tree git diff所看到的)而言,提交K与提交E相同。因此,第一个diff会忽略不正确的提交及其返回。比较BI'的第二个差异,将樱桃挑选的提交看作是一种变化,因此它包含了这种变化。组合第一个和第二个差异可以为您提供一个更改副本。

git log命令有点(或有时很多)

  

如果我查看其中一个文件的历史记录,在d2l_phase_4分支上,那么我看不到恢复或任何内容的痕迹,我只能看到樱桃选择了我做的提交。

现在我们已经进行了最后一次合并Lgit log应该向我们展示提交L的内容,L的第一和第二内容父母,他们的父母的内容,等等。

提交L是一个合并提交,因此它有两个父项KI'I'是您挑选的提交,因此当git log到达那一点时您会看到它。

K是一个简单的提交,其中包含一个父项J;但是J是一个包含两个父项EI的合并提交。您可能希望在此处看到提交I,并且 - 此部分变得棘手 - 有时您会

当你运行git log -- <pathspecs>时,你给Git一个隐含的论据,即(我认为)不能明确拼写,尽管--dense--sparse可能足够接近解释性目的。这些开启历史简化,历史简化抛出了一个&#34; side&#34;如果合并的话。在任何情况下,您还指示Git忽略在<pathspecs>参数中命名的所有 文件。

当Git查看commit L时,它是一个包含两个父项的合并提交,因此历史简化规则的这部分适用:

  

如果对任何父母不是TREESAME,则包括提交(但是   这可以更改,请参阅下面的--sparse。如果提交是一个   合并,对一位父母来说是TREESAME,只关注那位父母。 ...否则,跟随所有父母。

这&#34; TREESAME&#34;被定义为:抛弃除指定路径之外的所有路径之后,被修剪的提交树与其父项之一相同[&#39;树?因此,对于L,我们会在删除除您要询问的文件之外的所有文件后将其与KI'进行比较。如果L中的文件版本与I'中的文件版本相匹配 - 可能就是这样 - 那么此时git log会删除K,并且将不会再看到K 1}},也不是J,也不是J的父母,也不是ED也不是C。但是,它会跟随I'返回H,然后转到GF以及B,依此类推,然后返回历史记录。

如果L中的文件版本与K中的版本匹配,则git log会删除I';但由于I'肯定触及了该文件,因此Git仅在此处跟I'

要让Git向您展示完整的历史记录,您需要--full-history,而不是简化图表中找到的侧枝。 Git仍会将历史记录限制为触及文件的提交,但这次它会查看L的父母。

the git log documentation中有所有这些例子,但它绝对是眼睛上釉的材料。

另一件需要注意的事情是,git log经常没有提及合并提交本身,除非你指示它分裂&#34;他们(使用-m)或强制合并差异(-c--cc)。也就是说,它可以打印日志消息,但不显示任何更改,即使git show会显示某些内容(--cc样式合并差异)。