【问题标题】:Duplicate commits after rebase have been merged into the develop branchrebase 后的重复提交已合并到开发分支
【发布时间】:2017-03-25 21:23:06
【问题描述】:

我最近关闭了另一个开发人员正在开发的远程分支,我们称之为feature。然后我犯了一个错误,在develop(主要工作分支)上进行了rebase,我现在知道这是你不应该做的事情。 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 github merge rebase


    【解决方案1】:

    发生了什么

    “复制提交”正是git rebase 所做的。它复制一些提交,然后将分支指针打乱,以便“忘记”或“放弃”原始提交。 (但见下文。)

    这是git rebase 如何进行复制的说明。单个字母代表commit,右边的名字是branch names,实际上只指向一个 commit,即“分支的尖端”。每个提交都指向其父提交,即A--B 连接线实际上是指向左的箭头(对角线的箭头也仍然指向左侧,指向较早的提交,后来的提交指向右侧):

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

    这是“之前”图片,您只有“原始”提交。您现在决定git checkout branch1git rebase branch2,以便CD之后 E。但 Git 实际上根本无法更改原来的 C--D,所以它会将它们复制到新的副本,C'D',以及新的副本略有不同:它们出现在 E 之后(并且还使用您在 E 中所做的任何代码更改):

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

    在这里完全忘记原来的C--D 是可以的,但是如果您认为这是一个坏主意怎么办? rebase 将分支的原始值保留在“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的原件。

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

    如果您已经完成了变基——制作了副本—— 发布了它们(将它们推送到origin),那么你有点卡住了。无论如何,您都可以“撤消”您的变基,并请求所有共享origin 使用的其他人确保他们不要将您的C'-D' 类型副本用于任何事情,因为您将原件回来。

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

    撤消它

    所以,如果你 (a) 可以 并且 (b) 想要“撤消”你的 rebase,那么现在 reflog 或保存的 ORIG_HEAD 真的来了派上用场。让我们再次举第二个例子,看看我们忘记了branch3仍然记得原来的C-D提交后得到了什么:

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

    现在,假设我们从底行删除名称 branch1 并写入一个新的 &lt;-- 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)。1这会移动当前分支(我们刚刚检查过)以便它指向到给定的提交(branch1@{1},来自 reflog,或 ORIG_HEAD 或原始哈希 ID),让我们返回最终的图形。 --hard 设置我们的索引/暂存区域和我们的工作树,以匹配我们刚刚重新指向我们的分支的新提交。


    1这里的一般想法,在 Git 中一直重复出现,是我们必须命名一些特定的提交,Git 从中找到 rest 必要时提交。任何名称都有效:分支名称、HEAD 之类的名称、master@{1} 之类的 reflog 名称或原始提交哈希 ID。 Git 并不真正关心 如何 你告诉它“看这里的提交”;最终,Git 将该名称解析为那些丑陋的 SHA-1 哈希 ID,并使用它。

    【讨论】:

      【解决方案2】:

      使用git reflog 恢复您的更改。

      here 中阅读所有相关信息(如何恢复以前的头部/如何撤消更改):

      怎么办?

      输入 git reflog 并找出您想要返回的“最后一个好”sha-1。
      运行

      git reset <SHA-1> --hard
      

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

      【讨论】:

      • 我应该在 developfeature 上执行此操作,还是完全在另一个分支上执行此操作?
      • reflog 是每个仓库,所以如果你想将特定的分支开关恢复到你想要修复的所需分支
      猜你喜欢
      • 2021-03-28
      • 1970-01-01
      • 1970-01-01
      • 2012-03-05
      • 2014-10-14
      • 1970-01-01
      • 2020-04-04
      • 2015-12-22
      • 2021-12-02
      相关资源
      最近更新 更多