有两个步骤可以实现:
- 创建一个新的空提交
- 重写历史以从这个空提交开始
为方便起见,我们会将新的空提交放在临时分支 newroot。
1。创建一个新的空提交
有很多方法可以做到这一点。
仅使用管道
最干净的方法是使用 Git 的管道直接创建一个提交,这样可以避免接触工作副本或索引或签出哪个分支等。
-
为一个空目录创建一个树对象:
tree=`git hash-object -wt tree --stdin < /dev/null`
-
围绕它进行提交:
commit=`git commit-tree -m 'root commit' $tree`
-
创建对它的引用:
git branch newroot $commit
如果你对你的 shell 足够了解,你当然可以将整个过程重新安排成一行。
没有管道
使用常规瓷器命令,如果没有检查newroot 分支并重复更新索引和工作副本,您将无法创建空提交,没有充分的理由。但有些人可能会觉得这更容易理解:
git checkout --orphan newroot
git rm -rf .
git clean -fd
git commit --allow-empty -m 'root commit'
请注意,在缺少 --orphan 切换到 checkout 的非常旧版本的 Git 上,您必须将第一行替换为:
git symbolic-ref HEAD refs/heads/newroot
2。重写历史以从这个空提交开始
你有两个选择:变基,或干净的历史重写。
变基
git rebase --onto newroot --root master
这具有简单的优点。但是,它还会在分支上的每个最后一次提交时更新提交者名称和日期。
此外,对于一些极端情况历史,它甚至可能由于合并冲突而失败 - 尽管事实上您正在基于一个不包含任何内容的提交。
历史改写
更简洁的方法是重写分支。与git rebase 不同,您需要查找您的分支从哪个提交开始:
git replace <currentroot> --graft newroot
git filter-branch master
显然,重写发生在第二步;这是需要解释的第一步。 git replace 的作用是告诉 Git,每当它看到对要替换的对象的引用时,Git 应该查看该对象的替换。
使用--graft 开关,您告诉它的内容与正常情况略有不同。您是说还没有替换对象,但是您想用自己的精确副本替换<currentroot> 提交对象除了替换的父提交应该是那个(s) 您列出的(即newroot 提交)。然后git replace 继续为您创建此提交,然后声明该提交作为您原始提交的替换。
现在,如果您执行git log,您会看到事情已经如您所愿:分支从newroot 开始。
但是,请注意git replace 实际上并没有修改历史记录——它也不会传播到您的存储库之外。它只是将本地重定向添加到您的存储库,从一个对象到另一个对象。这意味着没有其他人看到这种替换的效果——只有你。
这就是为什么filter-branch 步骤是必要的。使用git replace,您可以创建一个精确的副本,其中包含为根提交调整过的父提交; git filter-branch 然后对所有后续提交也重复此过程。这就是历史真正被重写的地方,以便您可以分享它。