我想制作一个rebase来从我的历史记录中删除某个提交。我知道该怎么做。但是,如果我这样做,则提交时间戳设置为我完成rebase的那一刻。我希望提交保持时间戳。
我在这里看到了最后一个答案:https://stackoverflow.com/a/19522951/3995351,但它不起作用。
最后一个重要的命令只显示了
的新行>
所以我开了一个新问题。
答案 0 :(得分:9)
让我们说这是您要删除的提交的历史记录
... o - o - o - o ... ... o
^ ^ ^ ^
| | +- next |
| +- bad +-- master (HEAD)
start
其中:
bad
是您要删除的提交; start
是您要删除的提交的父级; next
是bad
之后的下一次提交;它很好,你想保留它和它之后的所有时间表;它将在rebase之后替换bad
。为了能够安全地删除bad
,在创建bad
时没有其他分支合并到{{1}之后的主时间轴中,这一点非常重要}。即通过从历史记录图中删除bad
及其与父提交和子提交的连接,您将获得两个断开连接的时间轴。
即使在bad
之后合并了另一个现有分支,也可能删除bad
。我没有检查这种情况,但由于合并提交,我预计会遇到一些障碍。
每个bad
提交都由使用提交的属性计算的哈希标识:内容,消息,作者和提交者日期以及电子邮件。
rebase始终会更改提交者日期。它也可以更改提交者电子邮件,提交消息和内容。
为了在rebase之后恢复原始提交者日期,我们需要将它们与一些可以在rebase之后识别每个提交的信息一起保存。
因为您想修改提交,所以提交内容在rebase期间会发生变化。添加或删除文件或提交会在以后的所有提交中更改内容。
这使我们没有唯一标识提交的属性,并且在期望的rebase期间不会更改。我们可以尝试使用两个或多个在rebase期间不会更改的属性。
电子邮件(作者和提交者)几乎没用。如果有一个人在项目上工作,则所有提交都是相同的,不能使用。保留的属性(在大多数提交中不同,不受rebase的影响)是作者日期和提交消息(第一行)。
如果对(作者日期,提交消息)为受rebase影响的所有提交提供唯一值,那么我们可以在之后恢复提交日期而不会出错。
有一种简单的方法可以验证(作者日期,提交消息)对是否对受影响的提交是唯一的。
运行以下两个命令:
git
如果它们显示相同的数字,那么您很幸运:该对(作者日期,提交消息)可用于唯一标识提交。请继续阅读。
如果数字不同(第一个命令将始终产生一个小于或等于第二个命令产生的数字),那么你就不走运了。
此命令
$ 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
"重写历史记录"是一种常见的误解,但事实上它只是生成一个替代历史记录行并确定它是正确的历史记录。它不会改变或删除"重写"提交;它们在数据库中仍然存在一段时间,并且可以在操作失败时恢复。
我们可以主动备份当前的历史记录行,以便在需要时轻松恢复。我们所要做的就是创建一个指向git
的新分支。这样,当master
将git rebase
移动到新时间轴时,仍可使用新分支访问旧时间轴。
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
的提交之间的历史记录,并且对于每次提交,它会在重写提交之前运行作为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)
以生成上面讨论的密钥对(作者日期,提交消息)。其输出作为参数传递给命令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
如果您对结果感到满意,可以删除备份分支和用于存储原始提交日期的文件:
$ git reset --hard old_master
这就是全部!
答案 1 :(得分:2)
所以,这是一个繁琐的方法(取决于你需要修改多少次提交),但我尝试了它并且它有效。当您执行交互式rebase时,请使用“e”标记每个提交,以便您可以对其进行编辑。这将导致git在每次提交后暂停。在每次暂停时,您可以指定使用的日期并继续使用以下提交:
GIT_COMMITTER_DATE="Wed Feb 16 14:00 2011 +0100" git commit --amend
git rebase --continue
这当然是后方的主要痛苦,你必须事先知道所有的提交日期,但如果你不能以任何其他方式做到这一点,那至少应该有效。