git不会合并-说“已经是最新的”。当不是

Lar*_*ell 1 git

我有一个从未遇到过的git问题。

我有2个分支,其中有2个不同的文件。
如果我将一个合并到另一个(在任一方向上),它会说“已经是最新的”。

为什么它认为它们是最新的,以及如何合并?

我读了这篇文章git merge不会合并(和其他),他们都说“如果它是最新的,那么您已经正确合并了”。

我意识到git 认为它们已合并,但不是-我在2个分支中有不同的文件。

即使我进行更改并合并,它仍然会显示“已经是最新的”。

我可以让git来“忘记”我已经将它们合并,然后再次合并吗?

我不知道在这种状态下会发生什么,但这是一场噩梦。必须有某种方法可以解决此问题。

更新我所做的:

我是唯一从事此工作的人。我被要求添加一些新功能,因此我创建了master,new_features分支,并开始进行开发。然后,我被告知将其搁置一旁,并开发更高优先级的新功能,因此我从master分支了另一个分支new_features2,然后开始着手开发这些功能。然后我被告知我们需要一些bug_fixes,然后我又在master分支中创建了一个bug_fixes。我修复了错误,并将bug_fixes合并为master。然后我回到工作new_features2,使该东西正常工作,将master合并到new_features2中,进行测试,然后将new_features2合并到master中。到目前为止一切都很好。

现在,我回到使用new_features的工作。我完成了这些工作,然后将master合并到new_features中。它确实合并了一些东西。我进行了测试,发现new_features2或bug_fixes都无法正常工作。我看一下合并后的代码,似乎new_features2和bug_fixes中的某些更改在new_features中,但并非全部。我尝试再次合并-它说“已经是最新的”。

tor*_*rek 5

大编辑:您可能想重新设置基准。

我将rebase部分放在顶部,但您可能需要先阅读其余内容。

变基

git中最有用的事情之一是挑选整个“分支”(一串以分支尖端结尾的提交),以有效地移动它们。也就是说,假设您现在喜欢樱桃采摘(如果需要,请参阅下文),假设您在提交图中有类似以下内容:

             o--o         <-- fix-bug-1234
            /    \
...--o--o--o---o--o--o    <-- develop
      \
       o--o--o--o         <-- feature
Run Code Online (Sandbox Code Playgroud)

feature是从前一段时间开始工作的,然后不得不进行一些紧急更改,develop并且将紧急错误修复程序合并到了现在;现在是恢复工作的时候了feature

不过,feature从的最新好东西开始开发该版本肯定会很好develop

因此,让我们做到这一点。我们首先基于以下技巧签出一个新的功能分支develop

git checkout -b new-feature develop
Run Code Online (Sandbox Code Playgroud)

给我们这个:

             o--o         <-- fix-bug-1234
            /    \
...--o--o--o---o--o--o    <-- develop, HEAD->new-feature
      \
       o--o--o--o         <-- feature
Run Code Online (Sandbox Code Playgroud)

现在我们简单地在所有四个feature提交中进行选择。我们可以编写,feature~4..feature但是这要求我们对提交进行计数,因此,由于我们对git set表示法一无所知,因此我们可以使用1develop..feature来找到相同的提交集。这使我们可以将其用作第二个git命令:

git cherry-pick develop..feature
Run Code Online (Sandbox Code Playgroud)

复制了这四个提交:

             o--o                   <-- fix-bug-1234
            /    \
...--o--o--o---o--o--o              <-- develop
      \               \
       \               o--o--o--o   <-- HEAD->new-feature
        \
         o--o--o--o                 <-- feature
Run Code Online (Sandbox Code Playgroud)

现在,我们重命名featureold-feature,然后重命名new-featurefeature

git branch -m feature old-feature
git branch -m new-feature feature
Run Code Online (Sandbox Code Playgroud)

或者,我们可以使用轻松完成所有这些操作git rebase。我们不必从a git checkout -b,a git cherry-pick和两个重命名步骤开始,我们只需执行一个,git checkout然后执行一个git rebase

git checkout feature
git rebase develop
Run Code Online (Sandbox Code Playgroud)

rebase命令使用我们的一个参数develop来执行以下操作:

  • 弄清楚哪些是挑剔的: develop..feature
  • 看看在哪里复制它们: develop

有很多更奇特的形式,rebase可以让我们从承诺去处分离樱桃提要的清单,但是对于典型情况,我们不需要这些。

请注意,当我们进行重新设置基准时,rebase命令通常会忽略复制中的合并提交,但是除非在设置符号(填充)中排除了复制提交,否则复制提交“在合并提交之后”develop..feature。当序列中有合并提交时,这会产生令人惊讶的结果。

此外,由于重新提交副本提交(这确实是一系列的樱桃选择操作),因此,如果是唯一拥有提交原件的人,则这样做仅100%安全。如果共享您的作品(通过抓取或推送)的人也拥有原件,则他们可能正在使用它们,并且他们可能会在重新定型的副本旁边重新引入这些原件。 如果您是唯一拥有原始作品的人,那么这不是问题。 如果那些确实拥有副本的其他人都知道有关您基于副本的副本的全部信息,那么他们可以自己解决问题(尽管他们可能不想这样做)。


1如果您知道此设置符号,则可能会丢失一些使git的工作变得更加轻松的东西。


您错过了合并的要点-或者说合并点缺少要执行的操作可能更准确。

首先让我们看一下合并点。

假设您和Alice在做两个不同的事情。您应该获得可向左和向右切片的小部件,而不是仅向右切片。爱丽丝应该使横梁保持得更好。

你们俩都从一个共同的基础开始,在这个基础上,小部件仅向右切片,而交叉开关中断。

本周,您设法使小部件向左切片,而Alice修复了交叉开关。因此,您(或爱丽丝)现在想将您的工作和她的工作结合起来:

$ git checkout larry-widget-fixes
$ git merge alice-crossbar-fixes
Run Code Online (Sandbox Code Playgroud)

如果这样做使您的所有文件与Alice的文件完全匹配,那将撤消本周的所有工作

因此,git merge 不会使事物匹配。取而代之的是,它返回到通用基础,以查看(1)您更改了什么,以及(2)Alice更改了什么。然后,它尝试将这两组更改合并为一个合并(合并)更改。

在这种情况下,它是通过她的更改添加到您的更改中来完成的(因为我们将她的工作合并到了您的工作中,而不是将您的工作合并到了她的工作中)。

请注意,合并基础(共同的起点)是此处的关键项目之一。

如果您真的不想合并怎么办?

假设您不想合并一些更改。您想要使某些文件匹配。

有多种方法可以执行此操作,具体取决于您想要的结果。让我们看一下其中的三种方式。

  1. 通过提取另一个版本,使一个文件匹配。

    假设在修复小部件时,您不小心摔破了垫片。爱丽丝的分支有一个很好的副本shims.data(就此而言,合并基础也是如此,但让我们在这里使用爱丽丝的):

    git checkout alice-crossbar-fixes -- shims.data
    
    Run Code Online (Sandbox Code Playgroud)

    shims.data将从Alice分支的尖端中提取文件的版本,现在您有了所需的版本。

  2. 只需通过提交ID撤消一次提交即可。

    假设您意识到自己已在commit中打破了规则badf00d,那么您在该处所做的所有其他更改也应该都被撤消,或者可以撤消它们。然后可以运行:

    git revert badf00d
    
    Run Code Online (Sandbox Code Playgroud)

    git revert命令将给定的提交变成补丁,然后反向应用补丁,以使该提交的效果未完成,然后从结果中进行新的提交。

  3. 您想完全放弃一切。您意识到,本周所做的所有操作都会使小部件向左和向右切片,但这是一个错误,因为小部件根本不应向右切片。在这里您可以使用git reset

    我不会举一个例子,因为这(a)这可能不是您想要的;(b)git reset过于复杂(git伙计们在一个命令中填充了大约五到八个逻辑上不同的操作);(c)StackOverflow上已经有很多很好的例子。(这git revert在第2步中同样适用,但是git revert要简单得多,所以我将其保留。)

如果合并不好怎么办?

这更加复杂。

请记住,在第一个示例中进行合并时,在您和Alice开始执行不同任务之前的一周初,我们有一个共同的起点:

...--o--*--o--o--o    <-- larry-widget-fixes
         \
          o-o-o-o-o   <-- alice-crossbar-fixes
Run Code Online (Sandbox Code Playgroud)

常见的合并基础是commit *

不过,一旦完成合并,我们就M1可以做到这一点(我将合并提交标记为便于查看):

...--o--o--o--o--o--M1   <-- larry-widget-fixes
         \         /
          o-o-o-o-o      <-- alice-crossbar-fixes
Run Code Online (Sandbox Code Playgroud)

现在,假设爱丽丝进行了更多修复,让我们也进行另一处更改:

...--o--o--o--o--o--M1-o     <-- larry-widget-fixes
         \         /
          o-o-o-o-*--o--o    <-- alice-crossbar-fixes
Run Code Online (Sandbox Code Playgroud)

现在,您通过再次合并来选择它们:

git merge alice-crossbar-fixes
Run Code Online (Sandbox Code Playgroud)

git找到通用的合并基础。这是两个分支上最近的提交,我*再次对其进行了标记。Git将找出自从以来Alice进行了哪些更改*,以及从以来您进行了哪些更改*,并尝试合并这些更改。您已经获得了Alice先前的所有更改-它们已经在您的分支中M1,-因此git不再查看这些更改是一件好事。Git只会尝试将Alice的新更改添加*到您的更改之后*(您的Alice更改版本)。

但是,如果您弄错了怎么办?如果您的Alice更改版本在M1中出现一堆错误文件怎么办?

此时,您必须选择是重试合并还是使用git的许多其他工具来修复问题。

重试合并

要重试合并,您必须备份到合并之前的某个点。例如,假设我们在之前的提交位置绘制了一个新箭头M1,如下所示:

                   ..........<-- retry
                  .
...--o--o--o--o--o--M1-o     <-- larry-widget-fixes
         \         /
          o-o-o-o-o--o--o    <-- alice-crossbar-fixes
Run Code Online (Sandbox Code Playgroud)

(想像它是一个箭头,而不是一些破烂的ASCII点)。我们可以将现有的分支标签保留在其中,指向它们的位置,只需retry通过执行以下操作添加此新标签:

git branch retry <commit-id> && git checkout retry
Run Code Online (Sandbox Code Playgroud)

要么:

git checkout -b retry <commit-id>
Run Code Online (Sandbox Code Playgroud)

(这两个命令本质上都做相同的事情,使我们retry指向所需的提交)。现在,如果我们这样做:

git merge alice-crossbar-fixes
Run Code Online (Sandbox Code Playgroud)

git会尝试合并的提交,在─ M1retry点,与(新,电流)的尖端alice-crossbar-fixes。Git将再次找到合并基础,这是这两个分支上最近的提交,这又是我们原始的合并基础。

换句话说,我们将重新进行合并,而忽略不良合并M1,也将忽略此后所做的新工作。

采摘樱桃

我们可以git checkout -b <commit>对任何特定的提交返回到该状态,然后开始执行诸如git cherry-pick重新添加特定提交的操作。

假设我们非常喜欢“之前提交” M1(看起来非常接近),因此我们在这里创建了新分支(我们称其为“其他”以外的东西)retry

git checkout -b rework <commit-ID>
Run Code Online (Sandbox Code Playgroud)

现在,我们可以从其他提交中进行选择,并使用提取它们所做的更改git cherry-pick。我们给它更多的提交ID,更多的197a3c2...字符串,并且它将刚提交之前的状态与该提交时的状态进行比较。结果是一个补丁,但实际上,它比常规的仅diff补丁要好一些,原因有两个:

  • 它附加了提交消息,git可以重复使用,并且
  • 它在提交图中有特定的位置,因此git可以(如有必要)在应用时找到合并基础并进行三向合并!通常这不是什么大问题,但在某些情况下非常方便。

忽略第二个优点,只是将其视为补丁程序,其git cherry-pick作用是将补丁程序应用于当前提交,然后进行新的提交,重新使用原始的提交消息。

如果您想以这种方式重新应用许多提交,则可以给出git cherry-pick多个提交,它将全部完成:

git cherry-pick xbranch~3..xbranch
Run Code Online (Sandbox Code Playgroud)

这适用于每个提交,xbranch除了xbranch~3早于或更早开始的提交外,即,我们倒退三个步骤xbranch(假设没有合并...),然后应用其余三个提交:

    N N  Y Y Y   [do we take it?]

    | |  | | |
    v v  v v v

...-o-o--o-o-o   <-- xbranch
...-o--o         <-- larry-widget-fixes (before cherry pick)
Run Code Online (Sandbox Code Playgroud)

(并在顶部查看变基)

  • 很棒的帖子。太感谢了。 (2认同)