【问题标题】:Replace a commit in git替换 git 中的提交
【发布时间】:2012-10-30 07:12:24
【问题描述】:

很久以前我有一个错误的提交,我想将它从 git 历史记录中完全删除,就好像它从未发生过一样。我知道提交 ID,比如说 1f020。我已经尝试过 git rebase 并删除它,但是在 rebase 时有很多冲突,所以不可能那样做。该提交是一个简单的 1 行代码更改并推送一些与项目无关的文件。 所以我想编写那 1 行代码更改并提交它,然后以某种方式将这个新提交替换为很久以前的那个。

【问题讨论】:

    标签: git github


    【解决方案1】:

    如果有问题的提交位于私有存储库中,那么您想要做的并不是什么大问题。重写已发布的 git 历史会让您的合作者感到恼火,因此请确保删除此行是值得的。

    git-rebase documentation 有一段很有帮助的文章。

    git rebase [...] [<--onto newbase>] [<upstream>] [<branch>]
    

    还可以使用 rebase 删除一系列提交。如果我们有以下情况:

    E---F---G---H---I---J  topicA
    

    然后是命令

    git rebase --onto topicA~5 topicA~3 topicA
    

    将导致删除提交 FG

    E---H'---I'---J'  topicA
    

    如果 FG 在某些方面存在缺陷,或者不应该是 topicA 的一部分,这很有用。请注意,--onto 的参数和 upstream 参数可以是任何有效的 commit-ish。

    假设你的历史是线性的并且有问题的提交在你的主分支中,你可以通过运行来调整上面的例子

    git rebase --onto 1f020~ 1f020 master
    

    对于更复杂的情况,使用交互式变基。您可能会发现跟随 an example that merges two commits 会有所帮助,但不要将提交标记为 s 用于壁球,而是删除整行以从您的历史记录中删除提交。

    【讨论】:

    • @sonnyhe2002 这取决于您的存储库的详细信息和有问题的提交。很难给出比“取决于情况”更好的答案。
    【解决方案2】:

    这有点复杂,但无论如何,它是这样的:

    分离头部并移动以仅在 AFTER 那个错误的提交之后提交。使用 git log 找出 1f020 之后的下一次提交。

    git checkout <SHA1-for-commit-just-after-bad-commit-1f020>
    

    移动 HEAD 以仅在 BEFORE 错误提交之前提交,但保留索引和工作树原样

    git reset --soft <SHA1-for-just-previous-to-bad-commit-1f020>
    

    重做提交只是 AFTER 重新使用提交消息的错误提交,但现在在提交之上只是 BEFORE 那个错误提交。因此,删除那个错误的提交

    git commit -C <SHA1-for-commit-just-after-bad-commit-1f020>
    

    将错误的提交之后重新应用到这个新的地方

    git rebase --onto HEAD <SHA1-for-commit-just-after-bad-commit-1f020> master
    

    【讨论】:

      【解决方案3】:

      你永远不应该从 git 的历史中删除提交。以后会遇到问题。

      但是,如果您知道自己在做什么,则可以在本地回溯历史记录,用当前提交替换该提交,然后再次重播所有提交 - 然后对您的存储库执行强制推送。

      但我强烈建议不要这样做。只需做一个新的提交,并忍受历史上有一个错误的提交。

      【讨论】:

      • 我确实知道如何回滚和重播我的所有提交,如上所述。但是有数百个提交要重放数百个冲突。我要求另一种解决方案。如何删除那个错误的提交,而不必重播前面的所有数百个提交。
      • @sonnyhe2002 可以这样做,但并不容易,需要编写脚本。详情见我的回答。
      【解决方案4】:

      所以,你想要的是:

      1. 从历史记录中删除错误提交,并且

      2. 不必重做删除提交后的所有合并。 IE。从已删除提交派生的每个提交都应保持原样,仅进行最小限度的更改以不包括从历史记录中删除的更改。

      基于git rebase --ontogit rebase -i 的解决方案在第二点上失败了,因为它们需要重做后来发生的所有合并。但是,理论上可以简单地重新创建所有这些其他提交,就好像有问题的提交从未发生过一样,只要错误提交足够小,以至于从其后续提交中恢复它本身就会产生冲突。

      正如迈克尔和其他人指出的那样,这样做是非常不可取的。这也是一项几乎可以肯定不值得付出努力的重大事业。但是,就教育价值而言,以下是实现目标的综合解决方案的概要:

      • 备份存储库。

      • 使用git rev-list 生成一个提交列表,该列表以错误提交开始,并通向所有可以访问该提交的分支头。

      • 初始化一个空映射以将旧提交映射到新提交。

      • 对于列表中的每个提交,执行以下操作:

        • 检查提交并从错误提交中恢复更改
        • 使用git write-tree从当前树中创建一个树对象
        • 使用git commit-tree 创建一个新的提交,其中包含新树、旧提交消息和使用上述映射从旧父级转换而来的父级(如果某些父级不在映射中,则使用旧父级)
        • 在提交映射中注册新提交。
      • 检查标签和标签对象,并重新创建它们以指向新的提交,查阅地图。

      • 查看分支负责人并调用git update-ref 将它们指向新的提交,并查阅地图。

      如果提交引入的单行更改(以及不必要的文件)与后续提交的更改不冲突,则此过程可以完全自动化,并且不需要手动重做以后提交的所有合并和冲突。

      缺点是它仍然需要重写所有后来的提交,如果它们已在其他地方共享,则使它们无效,并使提交消息中的提交引用无效,例如由git revert 生成的那些。

      【讨论】:

      • 好的,我该如何“初始化一个空映射以将旧提交映射到新提交”
      • @sonnyhe2002 澄清一下,“初始化空地图”不是指 git 概念,而是算法中的一个步骤。它告诉实现者创建映射,该映射稍后将用于将旧提交与新提交相关联。在 Python 中,映射将被实现为字典,在 Perl 中作为散列,在 Bash 中是关联数组。此地图将在以下步骤中填充和查找,因此必须事先对其进行初始化。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2013-01-13
      • 2017-08-26
      • 2017-12-15
      • 2018-02-10
      • 2021-06-18
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多