设置
假设这是您要删除的提交的历史记录
... o - o - o - o ... ... o
^ ^ ^ ^
| | +- next |
| +- bad +-- master (HEAD)
start
where:
-
bad
是您要删除的提交;
-
start
是您要删除的提交的父级;
-
next
是之后的下一次提交bad
;这很好,你想保留它以及它之后的所有时间线;它将取代bad
变基后。
先决条件
为了能够安全地移除bad
,重要的是当时没有其他分支存在bad
创建后合并到主时间线中bad
。 IE。通过删除bad
及其与历史图中父级和子级提交的连接,您将获得两个断开连接的时间线片段。
或许可以去除bad
即使之后合并了另一个现有分支bad
。我没有检查这种情况,但我预计由于合并提交会出现一些障碍。
The idea
Each git
提交由使用提交属性计算得出的哈希值来标识:内容、消息、作者和提交者日期以及电子邮件。
变基总是会更改提交者日期。它还可以更改提交者电子邮件、提交消息和内容。
为了在变基后恢复原始提交者日期,我们需要将它们与一些可以识别变基后每次提交的信息一起保存。
因为要修改提交,所以提交内容在变基期间会发生变化。添加或删除文件或提交会更改所有未来提交的内容。
这使得我们没有一个唯一标识提交并且在所需的变基期间不会更改的属性。我们可以尝试使用两个或多个在变基期间不会改变的属性。
电子邮件(作者和提交者)几乎没有用。如果只有一个人参与该项目,则他们对于所有提交都是相同的,并且不能使用。保留的属性(在大多数提交上都不同,不受变基影响)是作者日期 and 提交消息(第一行)。
如果这对(作者日期,提交消息)为受变基影响的所有提交提供唯一值,那么我们可以在之后恢复提交日期而不会出现错误。
验证是否可以安全完成
有一种简单的方法可以验证(作者日期,提交消息)对对于受影响的提交是否是唯一的。
运行以下两个命令:
$ git log --format="%aI %s" start...master | uniq | wc -l
$ git log --oneline start...master | wc -l
如果它们显示相同的数字,那么您很幸运:这对(作者日期,提交消息)可用于唯一标识提交。请继续阅读。
如果数字不同(第一个命令生成的数字始终小于或等于第二个命令生成的数字),那么您就不走运了。
提取修复变基后提交日期所需的信息
这个命令
$ git log --format="%H %cI %aI %s" start...master > /tmp/hashlist
提取以以下开头的所有提交的提交哈希、提交者日期(有效负载)、作者日期和提交消息(密钥)start
并将它们存储在文件中。
备份当前master
虽然这是一个常见的误解git
“重写历史”,实际上它只是生成一条替代历史线并确定它是正确的历史。它不会更改或删除“重写”的提交;它们仍会在数据库中存在一段时间,并且可以在操作失败时恢复。
我们可以主动备份当前历史行,以便在需要时轻松恢复。我们所要做的就是创建一个指向的新分支master
。这样,当git rebase
moves master
对于新的时间线,旧的时间线仍然可以使用新的分支访问。
$ git branch old_master
上面的命令创建一个名为old_master
这使得当前的时间线成为焦点,直到我们完成所有的改变并对新的世界秩序感到满意。
进行变基
删除提交bad
从历史来看,很简单:
$ git rebase --preserve-merges --onto start bad
修复提交日期
以下命令“重写”历史记录并使用我们之前保存的值更改提交者日期:
$ git filter-branch --env-filter 'export GIT_COMMITTER_DATE=$(fgrep -m 1 "$(git log -1 --format="%aI %s" $GIT_COMMIT)" /tmp/hashlist | cut -d" " -f2)' -f start...master
怎么运行的:
git
遍历标记的提交之间的历史记录start
and master
对于每个提交,它都会运行作为参数提供的命令--env-filter
在重写提交之前。它设置环境变量GIT_COMMIT
提交的哈希被重写。
既然我们已经做了rebase
修改了我们无法使用的所有提交的哈希值$GIT_COMMIT
直接识别提交的原始提交日期(因为$GIT_COMMIT
是由以下生成的提交git rebase
并且我们对他们的提交者日期不感兴趣)。
我们提供给的命令--env-filter
export GIT_COMMITTER_DATE=$(fgrep -m 1 "$(git log -1 --format="%aI %s" $GIT_COMMIT)" /tmp/hashlist | cut -d" " -f2)
runs git log -1 --format="%aI %s" $GIT_COMMIT
生成上面讨论的密钥对(作者日期、提交消息)。其输出作为参数传递给命令fgrep -m 1 "..." /tmp/hashlist | cut -d" " -f2
在先前保存的哈希值列表中找到该对(fgrep
)并从保存的行中提取原始提交日期(cut
)。最后,将提交日期的值存储在环境变量中GIT_COMMITTER_DATE
被使用的是git
重写提交。
确认
使用git log
再次命令
$ git log --format="%cI %aI %s" start...master
您可以验证重写的历史记录是否与原始历史记录匹配。如果您使用图形git
客户可以通过目视检查更轻松地检查结果。分支old_master
使旧的历史记录行在客户端中可见,您可以轻松比较每次提交的日期old_master
与相应的分支之一master
branch.
如果事情进展不顺利或者您需要修改程序,您可以通过以下方式轻松重新开始:
$ git reset --hard old_master
Cleanup
当您对结果感到满意时,您可以删除备份分支和用于存储原始提交日期的文件:
$ git branch -D old_master
$ rm /tmp/hashlist
就这样!