rebase 合并到 develop 分支后重复提交

Duplicate commits after rebase have been merged into the develop branch

我最近取消了另一个开发人员正在开发的远程分支,我们称之为 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 用户写下您的答案。

使用 git reflog 还原您的更改。

阅读 中的所有内容(如何恢复以前的头部/如何撤消更改):

怎么办?

键入 git reflog 并找出要返回的 "last good" sha-1。
运行

git reset <SHA-1> --hard

并且你回到了你犯错之前的上一次提交。

发生了什么事

"Copy commits" 正是 git rebase 所做的 。它复制一些提交,然后随机移动分支指针,以便 "forget" 或 "abandon" 原始提交。 (但见下文。)

下面是 git rebase 如何进行复制的说明。单个字母代表commit,右边的名字是branch names,其实只指向一个个commit,即"tip of the branch".每个提交都指向其父提交,即 A--B 连接线实际上是指向左的箭头(并且对角线方向的箭头也仍然指向左侧,指向较早的提交,后来的提交指向右侧):

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

这是 "before" 图片,您只有 "original" 次提交。您现在决定 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

如果我们现在 "rebase" 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 就那么简单——"get lost" 和复制 C--D [=139 很容易=]再次 错误)。

发生这种情况时

每当你复制你或其他人所做的提交时,你都会遇到这个问题,你和"someone else"(可能是"other you")也仍在使用原件。这发生在我们的提交 F 中,例如:我们仍在使用原始的 C--D 链。我们可以通过制作一个新的 F' 并使用它来解决这个问题, 只要 我们是唯一使用 branch3 的人。但是,如果 branch3 发布 ,或者就此而言,如果我们已发布 branch1,那么其他人可能会将它们作为 origin/branch1origin/branch3,我们已经失去了对 C--D.

原始副本的控制

因此,标准建议是仅对 private(未发布)提交进行变基,因为您知道谁在使用它们——当然只有您自己——并且您可以检查自己并确保您没有使用它们,或者可以复制它们,因为您还计划复制或以其他方式重新提交 F.

如果您已经完成了变基——制作了副本—— 发布了它们(将它们推送到 origin),你就有点卡住了。无论如何你都可以 "undo" 你的变基,并恳求所有共享 origin 的人确保 他们 不使用你的 C'-D' 类型复制任何东西,因为你要把原件放回去。

(对于更高级的用户组,您甚至可以同意某些分支会定期进行变基,并且您和他们都必须认识到这种情况何时发生,并且 所有然后你会注意切换到新的提交副本。但是,这可能不是你现在想要做的!)

撤消它

所以,如果你 (a) 可以 和 (b) 想要 "undo" 你的 rebase,现在 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',那就别看了。将此图与原始图进行比较,瞧!这就是你想要的!

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

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

看看 ORIG_HEAD 是否为您提供了正确的哈希值。如果没有,请尝试 git reflog branch1(在此处查看 branch1 的特定 reflog)来查找哈希值,然后使用:

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

(或剪切并粘贴原始哈希而不是使用 branch1@{1})。找到所需的 "original" 提交后,您可以:

$ 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)。1 移动当前分支(我们刚刚检查过)以便它指向给定的提交(branch1@{1},来自 reflog,或 ORIG_HEAD 或原始哈希 ID),让我们得到最终的图形绘制。 --hard 设置我们的 index/staging-area 和我们的工作树,以匹配我们刚刚重新指向分支的新提交。


1这里的一般想法,在Git中一直重复出现,是我们必须命名一些特定的提交,Git 从中找到 rest 的提交(如有必要)。任何名称都可以:分支名称、HEAD 之类的名称、master@{1} 之类的引用日志名称或原始提交哈希 ID。 Git 并不关心 你怎么说 "look at this here commit";最终,Git 将该名称解析为那些又大又丑的 SHA-1 哈希 ID,并使用它。