在将衍合后的重复提交合并到develop分支中。

8
我最近拉取了另一个开发者正在处理的远程分支,我们称之为“feature”。然后我犯了一个错误,将其变基到“develop”——主要工作分支——现在我知道这是不应该做的事情。此后,“feature”分支已合并到“develop”中。
现在我的问题是,“develop”有一个奇怪的Git历史记录。所有来自“feature”的提交似乎都是重复的,它们出现了两次。但是,它们具有不同的提交ID。
我的历史记录现在看起来像这样(ID仅用于演示目的):
0007 commit from feature #3  <--- these commits are duplicated
0006 commit from feature #2
0005 commit from feature #1
0004 different commit from another branch #2
0004 different commit from another branch #1
0002 commit from feature #3
0002 commit from feature #2
0001 commit from feature #1

我犯了一个愚蠢的错误!有什么可以做来解决这个问题吗?历史记录看起来很难看,但所有正确的代码似乎都在那里。我能删除重复的提交吗?或者是否有其他方法来清理历史记录?

请为一个不太经验的Git用户编写您的答案。

2个回答

13

发生了什么

"复制提交"就是git rebase所做的事情。它会复制一些提交,然后重新排列分支指针以"遗忘"或"放弃"原始提交。(但请参见下面的内容。)

这是一个说明git rebase如何进行复制的示例。单个字母表示提交,右侧的名称是分支名称,实际上只指向一个提交,即"分支尖端"。每个提交都指回其父提交,即A--B连接器线实际上应该是左指向箭头(而那些斜着的箭头也指向左边,指向更早的提交,在更晚的提交方向朝右):

     C--D   <-- branch1
    /
A--B
    \
     E      <-- branch2

这是“之前”的图片,其中你仅有“原始”的提交记录。现在,你决定使用git checkout branch1git rebase branch2,以便CDE之后。但是Git实际上无法改变原始的C--D,因此它会将它们复制到新的副本C'D'中,新的副本略有不同:它们在E之后(并且还使用您对E所做的任何代码更改):
     C--D      [abandoned]
    /
A--B
    \
     E         <-- branch2
      \
       C'-D'   <-- branch1

完全忘记原始的C--D在这里是可以的,但如果您最终认为这是个坏主意怎么办?重新设置基础会在“reflogs”中保留该分支的原始值以进行记忆。它还使用特殊名称ORIG_HEAD。这样做更容易,但只有一个ORIG_HEAD,而可能有无限多个reflog条目。默认情况下,reflog条目将被保留至少30天,让您有时间改变主意。回到第二张图,想象一下添加了ORIG_HEAD后会发生什么。
现在,您遇到的问题是因为不仅是分支名称记住先前的提交。每个提交还通过那些连接向左指向的箭头来记住自己的先前提交。因此,让我们看看如果有另一个名称或某个(合并)提交记住CD会发生什么。例如,如果我们有这个更为复杂的起始图:
    .-----F    <-- branch3
   /     /
  /  C--D      <-- branch1
 /  /
A--B
    \
     E         <-- branch2

如果我们现在“变基”branch1,我们会得到这样的结果:
    .-----F    <-- branch3
   /     /
  /  C--D      [ORIG_HEAD and reflog]
 /  /
A--B
    \
     E         <-- branch2
      \
       C'-D'   <-- branch1
F是一个合并提交:它指向提交A和提交D。因此它保留了原始的D,这样就保留了原始的C,使得我们有点乱。
如果F是一个普通的提交,只指向D,我们将看到同样的问题。不过普通的提交更容易复制,所以如果F不是合并(如果我们的F只指向D而不是A),我们也可以仔细地重新定义branch3,将F复制到F',其中F'在我们新的D'之后。重新执行合并也是可能的,但是这有点棘手(无论如何正确地复制F都不容易——很容易出错并再次复制C--D)。
当发生这种情况时,您会遇到这个问题,无论是您还是其他人都复制了您或其他人制作的提交,并且您和“其他人”(也许是“另一个您”)也仍在使用原始提交。例如,我们的提交F就发生了这种情况:我们仍在使用原始的C--D链。我们可以通过创建一个新的F'并使用它来解决这个问题,只要我们是唯一使用branch3的人。但是,如果branch3已发布,或者我们已经发布了branch1,以便其他人可能将它们作为origin/branch1origin/branch3,那么我们就失去了对C--D原始副本的控制。
因此,标准建议是仅重新定义私有(未发布)提交,因为您知道谁在使用它们——当然只有您——您可以与自己核对并确保您没有在使用它们,或者可以复制它们,因为您还计划复制或重新执行像F这样的提交。
如果您已经重新定义了(复制了)提交,并且将它们发布(推送到origin),那么您会陷入困境。无论如何,您都可以“撤消”您的重新定义,并请求与您共享origin使用权的每个人确保他们不会将您的C'-D'类型副本用于任何事情,因为您正在放回原始副本。
对于更高级的用户组,您甚至可以一致同意定期重新整合某些分支,并且您和他们必须都认识到这一点,然后您和他们都将注意切换到新的提交副本。但是,这可能不是您现在想要做的事情!
撤销它
因此,如果您(a)能够并且(b)希望“撤消”您的重新整合,那么现在reflog或保存的ORIG_HEAD真的非常有用。让我们再次看一下第二个示例,并查看在我们忘记branch3仍然记得原始C-D提交之后我们拥有什么:
    .-----F    <-- branch3
   /     /
  /  C--D      [ORIG_HEAD and reflog]
 /  /
A--B
    \
     E         <-- branch2
      \
       C'-D'   <-- branch1

现在,想象一下我们从底部行中删除名称为branch1,并写入新的<-- branch1指向提交D

    .-----F    <-- branch3
   /     /
  /  C--D      <-- branch1
 /  /
A--B
    \
     E         <-- branch2
      \
       C'-D'   [abandoned]

既然我们已经放弃了C'-D',就不要再看它了。将此图与原始图进行比较,哇!这就是你想要的结果!

像这样任意移动分支标签的命令是git reset(它移动当前分支,所以您必须在branch1上)。在reflog中查找D的原始提交哈希值,或检查ORIG_HEAD是否正确,或使用reflog拼写识别提交D。(对于新手,我发现复制和粘贴原始哈希是最好的方法)例如,尝试:

$ git log --graph --decorate --oneline ORIG_HEAD

检查使用ORIG_HEAD获取的哈希值是否正确。如果不正确,请尝试使用git reflog branch1(这里是查看特定的branch1引用日志)来查找哈希值,然后使用以下命令:

$ git log --graph --decorate --oneline branch1@{1}

(或者剪切并粘贴原始哈希而不是使用branch1@{1})。一旦找到所需的“原始”提交,您可以执行以下操作:

$ git status     # to make sure you're on the right branch
                 # and that everything is clean, because
                 # "git reset --hard" wipes out in-progress work!
$ git reset --hard ORIG_HEAD

可以把branch1@{1}或者原始哈希ID放在ORIG_HEAD的位置。这将使当前分支(我们刚刚检查过的)指向给定的提交(从reflog中获取的branch1@{1}ORIG_HEAD或原始哈希ID),从而获得最终的图形绘制结果。使用--hard选项将索引/暂存区和工作树都设置为与我们刚刚重新指向的新提交匹配。


Git中经常重复出现的一个基本思想是,我们必须命名一些特定的提交,从中Git可以找到必要的其他提交。任何名称都可以使用:分支名称、像HEAD这样的名称、类似于master@{1}的reflog名称或原始提交哈希ID。Git并不关心你如何告诉它“看这个提交”,最终Git会将该名称解析为其中一个大而丑陋的SHA-1哈希ID,并使用它。


2

使用 git reflog 命令来撤销您的更改。

这里详细了解如何恢复之前的 HEAD / 如何撤消更改:

怎么做?

输入 git reflog 命令,并查找您想要回到的“最后一个好”的 SHA-1。
运行

git reset <SHA-1> --hard

你可以回到之前的提交,恢复到你犯错误之前的状态。


我应该在develop还是feature分支上进行此操作,或者完全使用另一个分支? - shrewdbeans
1
reflog 是每个仓库独立的,因此如果您想要还原特定的分支,请切换到您希望修复的分支。 - CodeWizard

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接