有两个步骤来实现这一点:
- 创建一个新的空提交
- 重写历史以从这个空提交开始
为方便起见,我们会将新的空提交放在临时分支newroot
上。
1.创建一个新的空提交
有多种方法可以做到这一点。
仅使用管道
最干净的方法是使用 Git 的管道直接创建提交,避免接触工作副本或索引或签出哪个分支等。
为空目录创建一个树对象:
tree=`git hash-object -wt tree --stdin < /dev/null`
围绕它包裹一个提交:
commit=`git commit-tree -m 'root commit' $tree`
创建对它的引用:
git branch newroot $commit
如果您足够了解您的外壳,您当然可以将整个过程重新安排成一个单行程序。
没有管道
使用常规瓷器命令,您无法在不检查newroot
分支并重复更新索引和工作副本的情况下创建空提交,没有充分的理由。但有些人可能会觉得这更容易理解:
git checkout --orphan newroot
git rm -rf .
git clean -fd
git commit --allow-empty -m 'root commit'
请注意,在缺少--orphan
switch 的非常旧版本的 Git 上checkout
,您必须将第一行替换为:
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>
它自己的精确副本替换提交对象,除了替换的父提交应该是你列出的那个(即newroot
提交)。然后git replace
继续为您创建此提交,然后声明该提交作为您原始提交的替换。
现在,如果您执行 a git log
,您将看到事情已经如您所愿:分支从newroot
.
但是,请注意,git replace
它实际上并没有修改历史记录——也不会传播到您的存储库之外。它只是将本地重定向添加到您的存储库,从一个对象到另一个对象。这意味着没有其他人看到这种替换的效果——只有你。
这就是为什么该filter-branch
步骤是必要的。随着git replace
您为根提交创建具有调整的父提交的精确副本;git filter-branch
然后对所有以下提交也重复此过程。那是历史实际上被重写的地方,以便您可以分享它。