【问题标题】:Can you change which commit is the root commit in git?您可以更改哪个提交是 git 中的根提交吗?
【发布时间】:2021-07-22 21:21:45
【问题描述】:

假设您在公开之前设置了一个存储库,并且在此过程中您已经进行了许多提交。当需要将该存储库更改为公开时​​,您不希望将原始提交公开,而是希望存储库及其所有日志从最近的提交开始。这可能吗?

【问题讨论】:

  • 一个选项 - 在 HEAD 开始一个新的分支,叫做 history 来包含你所有的旧提交。然后在您的主分支上,git rebase -i 并将所有提交压缩为一个提交,然后仅推送该压缩分支。
  • 注意:我的这个答案Make a commit the root commit of a git repo (Delete history) 处理git filter-branch,尽管它有一些缺点,现在git filter-repo 命令是首选。
  • 创建一个存储库(使用git clone --depth 1之类的东西)进行共享可能比修改现有存储库更容易。
  • @0x5453 效果很好,谢谢!我在最近的提交上做了git rebase -i --root 然后pick 和所有其他提交上的squash。然后我做了git commit --amend -m "Initial commit.",最后是git push --force

标签: git


【解决方案1】:

有一些其他问题的链接可能是规范的,但快速浏览它们并没有发现明显的解决方案。

提交是一个不可变对象,其属性之一是其父对象,您不能更改提交以使其没有父对象。但是,您可以简单地创建一个使用相同树的新提交。例如:

$ h=$( echo 'Initial commit' | git commit-tree HEAD^{tree} )
$ git reset --hard $h > /dev/null
$ git gc

这将使用提交消息“初始提交”创建一个新提交,并将其哈希存储在 $h 中。新提交没有父级。然后重置设置当前分支指向这个新的提交。 git gc 清除所有孤立对象。如果您想删除所有旧的 cruft,请在执行 git gc 之前删除所有标签和分支,以便所有其他提交都被垃圾收集。

【讨论】:

    【解决方案2】:

    这个问题存在一种基本错误,因为它假设只有一个根提交。事实上,“rootness”是提交的一个属性:当且仅当它没有父提交时,提交才是根提交。

    每当您使用 git commit 进行新提交时,Git 都会使该新提交使用 当前 提交的哈希 ID 作为其(第一个,通常也是唯一的)父提交。

    作为一种特殊情况——在一个新的、完全空的、还没有提交的存储库中是必需的——Git 允许你在它所调用的地方,不同地,一个未出生的分支或一个孤儿分支。 “孤儿”一词是出现在git checkoutgit switch 命令中的词:

    git checkout --orphan new-branch
    

    和:

    git switch --orphan new-branch
    

    例如。不过,“未出生”形容词可能是更高级的形容词,因为它描述了处于此模式时的实际状态:这两个命令实际上根本没有创建新分支。当你处于这种模式时,你在一个不存在的分支上!

    在这种模式下——当你在一个未出生的分支上——git commit 命令将创建一个新的 root 提交,即一个没有父级的提交。这个新的根提交的创建导致分支出现,新的分支名称现在标识新的根提交。因此,实现您想要的结果的一种方法是:

    git checkout master        # if needed; use main if appropriate, etc
    git checkout --orphan new
    git commit
    

    然后将旧的mastermain 重命名,或将其完全删除,然后将new 重命名为mastermain

    这使用了一些技巧,我将在下一段中描述,并达到与William Pursell's answer 相同的结果,这也很好。请注意,您必须在此处使用git checkout --orphan,而不是git switch --orphan,这是诀窍的一部分。

    这里的诀窍是 git commit 从 Git 的 index 中的任何内容构建新提交。 git checkout --orphan 命令不会触及 Git 的索引,所以索引中的内容是刚才在您运行git checkout --orphan 之前索引中的内容。这就是为什么我们可能需要一个初始的 git checkout mastergit checkout main: 来填写 Git 的索引(和你的工作树)。

    git switch --orphan 命令具有清空 Git 的索引(以及您的工作树)的副作用。因此,这对于创建一个新的 empty 提交很有用:它不会重用 current 提交中的文件。 git checkout --orphan 命令不会清空 Git 的索引,因此它可以很好地创建与当前提交完全匹配的新提交。由于这两种命令都不是人们每天都在使用的,因此这些微妙之处可能会被忽视。

    大多数存储库可能只有一个根提交。任何非空(和非浅1)存储库都有至少一个根提交,但根提交的数量仅受提交总数的限制。


    1浅层克隆是一种忽略非浅层 Git 存储库中的一个或多个提交的提交,这些提交是这些提交的最终来源。为此,Git 插入了一个标记“graft points”的文件,并且不打扰他们的父母。生成的提交有父提交,但 Git 的某些部分假装它们没有,所以这些浅移植点既可以作为根提交,也可以作为非根提交,具体取决于哪些代码试图对它们做什么。但是,除了使用浅层存储库来节省空间和/或节省时间之外,最好忽略这种复杂性。

    【讨论】:

      【解决方案3】:

      也许我非常密集,但我会确保我已经检查了所需的初始状态(例如git switch master),然后将存储库部分扔掉(rm -rf .git)。现在从 git initgit add .git commit -minitial 重新开始。将其推送到新的公共存储库。

      【讨论】:

        【解决方案4】:

        最简单和最干净的方法是从当前提交开始创建一个新分支,并且没有任何历史记录:

        git checkout --orphan new-master
        git commit -m "Initial commit"
        

        这将让您将新旧历史记录并行保存在同一个存储库中(即多个根目录)。当你推送到公共远程时,你只需要确保你总是只推送新的分支/标签,而不是任何旧的分支/标签。如果您愿意,您还可以重命名分支,以便像往常一样将您的“新主人”称为“主人”。

        这很好用,但很容易出错,由于内部优化,即使是 git 实现也可能会上传一些“旧”内容。熟练的攻击者可能能够恢复一些旧的提交,即使它们不是直接可见的。为了确保没有遗留任何旧的东西,您可以删除所有旧的分支、标签、远程和 reflog,然后执行git gc --aggressive。同样,您可以将“新”和“旧”内容拆分到单独的存储库中 - 保持旧历史不公开,新历史干净,并且仍然可以单独使用它们。

        【讨论】:

          猜你喜欢
          • 2016-02-08
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2017-09-09
          • 2018-12-07
          • 2012-11-22
          • 1970-01-01
          相关资源
          最近更新 更多