你能改变哪一个提交是git中的根提交吗



假设您正在设置一个存储库,然后再将其公开,在这样做的过程中,您已经进行了多次提交。当需要将存储库更改为公共时,您不希望原始提交被公开,而是希望存储库及其所有日志从最近的提交开始。这可能吗?

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

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

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

这创建了具有提交消息"的新提交;初始提交";并将其散列存储在CCD_ 1中。新提交没有父级。然后重置会将当前分支设置为指向此新提交。git gc会清除任何孤立的对象。如果您想删除所有旧的cruft,请在执行git gc之前删除所有标记和分支,以便垃圾收集所有其他提交。

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

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

作为一种特殊情况——这在一个新的、完全空的、还没有提交的存储库中是必要的——Git允许你使用它所称的各种方法,比如未出生的分支孤儿分支

git checkout --orphan new-branch

和:

git switch --orphan new-branch

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

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

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

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

这使用了一个技巧,我将在下一段中描述,并获得了与William Pursell的答案相同的结果,这也很好。请注意,必须在此处使用git checkout --orphan,而不是git switch --orphan,这是技巧的一部分。

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

git switch --orphan命令的副作用是清空Git的索引

(以及工作树)。因此,这有利于创建一个新的提交:一个不重复使用当前提交中的文件的提交。git checkout --orphan命令不会清空Git的索引,因此它非常适合创建与当前提交完全匹配的新提交。由于这两个命令都不是人们每天都在使用的,所以这些微妙之处可能会被忽视。

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


1浅层克隆是指省略非浅层Git存储库中存在的一个或多个提交的克隆,该存储库是这些提交的最终来源。为此,Git插入一个标记为";移植物点";,而且不用麻烦他们的父母。生成的提交有父级,但Git的某些部分假装没有,所以这些浅移植点既可以作为根提交,也可以作为非根提交,这取决于代码试图对它们做什么。不过,除了使用浅存储库节省空间和/或时间外,最好忽略这种复杂性。

也许我很密集,但我只会确保我已经签出了所需的初始状态(例如git switch master),然后扔掉存储库部分(rm -rf .git)。现在从git initgit add .git commit -minitial开始。将其推向新的公开回购。

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

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

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

这工作得很好,但很容易出错,即使是git实现也可能会上传一些您的"旧的";suff由于内部优化。熟练的攻击者可能能够恢复一些旧的提交,即使它们不直接可见。为了确保没有遗留任何旧东西,您可以删除所有旧的分支、标记、远程和reflog,然后执行git gc --aggressive。类似地,您可以将您的";新的";以及";旧的";把东西放在单独的存储库中——保持你的旧历史记录是私有的,新历史记录是干净的,并且仍然可以单独使用它们。