【问题标题】:Remove a Git commit with no parent删除没有父级的 Git 提交
【发布时间】:2017-02-23 21:53:16
【问题描述】:

我正在教一门关于 Git 的课程。我不知道怎么做,但我的一个学生设法连续获得三个提交以前没有历史记录!学生甚至设法将这三个提交的行合并到master(在学生研究并发现--allow-unrelated-histories之后),但是在历史树中可以看到,而不是拆分master,这三个提交只是像尾巴一样挂在那里。

之前发生的一件事是,在尝试将存储库 fooproj 克隆到单独的目录 fooproj-copy 时,学生不小心将其克隆到了fooproj 的子目录中,因此 Bitbucket将其显示为远程存储库中的子项目。不过,我让学生删除了这个子目录fooproj-copy,然后推送新的提交,所以我认为一切都很好。我不知道他们是如何在没有父母的情况下获得提交的。

所以我说我会清除这三个提交(即使它们已经合并到 master 中)。我做了一个git reset --hard HEAD~1,它沿着那一行提交。我又做了一个git reset --hard HEAD~1,它一直在继续。但是又多了一个git reset --hard HEAD~1,我已经排到了终点;由于这三个提交没有更多的父母,我得到这个错误:

fatal: ambiguous argument 'HEAD~1': unknown revision or path not in the working tree.

所以通常如果我想丢弃一些提交,我只需对提交进行硬重置之前。但在这种情况下,在我想丢弃的提交之前没有提交。我怎样才能摆脱它们?

【问题讨论】:

    标签: git parent reset history


    【解决方案1】:

    把它画成图表:

           D
            \
    A--B--C--M--N   <-- master
               /
              E
    

    提交 A、D 和 E 都是根提交(没有父提交)。

    根提交很容易进行:git checkout --orphan 将您置于未出生的分支上,以便该分支上的下一次提交创建分支本身并创建新的根提交。或者,来自另一个具有无关历史记录的存储库的git fetch 获得以根提交终止的提交链(或复杂图)。或者,git commit-tree 可以使用任意父项编写新提交。

    你不能真正删除这些提交——或者任何提交,真的——直接。您所能做的就是让它们无法访问。

    如果至少有一个名称指向至少一个提交,并且通过其祖先链最终将您引导到 X,则该提交 X 是可访问的。在这种情况下,名称master 直接指向提交N,即合并。 N 指向E 和M,因此两者都可以访问。 M 指向C 和D,因此两者都可以访问。

    在这种情况下,如果您将master 指向C,则所有D、M、E 和N 都将无法访问。因此,它们最终将过期并被垃圾收集。但是如果在N 之后有一个提交——我们称之为F——你想保留它,你就无能为力了。您可以复制该提交:

           D
            \
    A--B--C--M--N--F   [abandoned]
           \   /
            \ E
             \
              F'       <-- master
    

    并开始使用F',F 的副本,作为master 的提示——但这意味着您没有保留F。你只保留一个不同的提交F'。

    【讨论】:

      【解决方案2】:

      首先,根据您的说法,您可能不应该在 Git 上教授课程。本身就已经非常难了,如果是不懂的人教的话我不知道学生会怎么做(我不是要人身攻击你,你可能是好意已经结束了教授这门课是有充分理由的,但我真的认为由熟悉它的人来教授这门课很重要)。


      现在,从字面上回答这个问题,要删除没有父级的 Git 提交,您只需确保不再有任何对它的引用

      完成后 git 会在一段时间后自动删除其文件;如果您想立即完成它(可能是因为该提交或一系列提交占用了大量空间),您可以使用git gc --prune=all。

      因此,在您的情况下,如果这些提交似乎不属于其他分支并且没有任何标签,您只需将您的分支重置到未引用该提交的历史记录中的某个点。


      但是如果您想要解决存储库的特定问题:

      从你所说的看来,学生所做的合并提交似乎将学生的最后一次提交作为其第一个父级,因此至少最初他将 ma​​ster 合并到他的分支中。
      ~ 命令遵循 first 父母,因此当您执行第一个 git reset --hard HEAD~1 时,您将 master 重置为学生的提交行(并且使用以下 2 个命令,您达到了他的第一次提交)。

      所以现在在那个存储库中,您可能已经丢失了 您在 master 上的所有其他提交。

      如果您有另一个克隆,最好还是回到它,否则您的原始提交行可能仍会被您的 reflog 引用,因此仍然存在。

      下面我假设你在你的学生合并后没有对master进行其他提交,从你所说的看来是这样。

      您似乎没有使用任何图形存储库浏览器,如果是这样,请键入 gitk --all 并使用它来查看您的分支/引用的确切状态,并按照它来执行您将要执行的操作(您必须每次修改后用 F5 手动刷新)。如果没有图形浏览器,基本上不可能很好地处理 git,但是您尝试将许多选项传递给 git log。

      键入 git reflog 并查看它是否列出了您的提交。您需要找到学生的合并提交或之前的提交。

      为确保不丢失任何其他内容,请先在您当前所在的位置添加一个标签 (git tag temp1)。

      然后,如果您找到了学生的合并提交,请先标记它(git tag temp-studentmerge &lt;sha1-of-the-commit&gt;);代替 放置该提交的(缩写)sha1,当然(它是 git reflog 左侧第一列中列出的内容)。

      然后执行git reset --hard temp-studentmerge。更新 gitk;您现在应该再次看到所有提交。

      现在您必须重置为正确的合并父级;最简单和最安全的选择是在图形浏览器中查看它的 sha1,然后执行git reset --hard &lt;sha1&gt;。否则,根据您的描述,git reset --hard HEAD^2 应该可以工作。
      ^<n> 表示法引用第 个父项,而 ~ 仅引用第一个父项。

      现在检查一切是否正确,然后删​​除临时标签(git tag -d temp1 和 git tag -d temp-studentmerge)。

      如果您使用 gitk,如果您执行了简单的 F5,您仍然会看到学生的提交,而是执行 Shift-F5(重新加载),您将不再看到它们。

      提交对象实际上应该仍然在您的存储库中的某个地方,因为只有当它们超过某个时间时,git才会删除不再引用的对象(我实际上不确定细节)。
      如果出于某种原因您现在想删除它们的任何痕迹,您可以使用git gc --prune=all。

      【讨论】:

      • 这里的要点是我只需要确保没有对这三个提交的引用。 (我知道如何重置为特定提交,但感谢您的完整解释。)这让我找到了关键:我还需要从学生执行的合并中删除 origin/master 和 origin/HEAD。之后,随着重置,额外的提交消失了,我强制推送远程存储库。
      • @GarretWilson 我不明白,但如果你解决了你的问题就可以了
      猜你喜欢
      • 2016-11-09
      • 2018-03-22
      • 2022-07-03
      • 1970-01-01
      • 2023-03-06
      • 2011-05-09
      • 2017-07-09
      • 2011-09-03
      • 2018-10-18
      相关资源
      最近更新 更多