我看到了这里的最后一个答案:https://dev59.com/v3A85IYBdhLWcg3wCe5Z#19522951,但它并没有起作用。
最后一个重要的命令只显示了一个新行。
>
所以我要开一个新的问题。
>
假设这是与您想要删除的提交相关的历史记录
... o - o - o - o ... ... o
^ ^ ^ ^
| | +- next |
| +- bad +-- master (HEAD)
start
其中:
bad
是你想要移除的提交;start
是你想要移除提交的父级;next
是bad
之后的下一个提交;它是好的,你想要保留它和它之后的所有时间线;在rebase之后,它将替换bad
。为了能够安全地移除bad
,重要的是,在创建bad
时不存在其他分支已合并到主时间线之后。也就是说,通过从历史图中删除bad
及其与其父级和子级提交的连接,您会得到两个不相关的时间线片段。
即使在bad
之后合并了另一个现有分支,可能仍然可以删除bad
。我没有检查过这种情况,但我预计由于合并提交而会遇到一些障碍。
每个git
提交都由一个哈希标识,该标识使用提交的属性(内容、消息、作者和提交者日期和电子邮件)计算。
重新贴标签始终会更改提交者日期。它还可以更改提交者电子邮件、提交消息和内容。
为了在rebase之后恢复原始的提交日期,我们需要将其与一些可以在rebase后标识每个提交的信息一起保存。
因为你想修改一个提交,所以提交内容在rebase期间会发生变化。添加或删除文件或提交会更改所有未来提交的内容。
这使我们没有一个唯一标识提交并在所需的rebase过程中不改变的属性。我们可以尝试使用两个或多个在rebase期间不发生变化的属性。
电子邮件(作者和提交者)几乎没有用处。如果有单个人在项目上工作,则它们对所有提交都是相同的,无法使用。留下的属性(在大多数提交上不同,在rebase上不受影响)是作者日期和提交消息(第一行)。
如果对于受rebase影响的所有提交,(作者日期,提交消息)提供唯一值,则我们可以在之后恢复提交日期而不出错。
有一种简单的方法可以验证受影响的提交的(作者日期,提交消息)对是否唯一。
运行以下两个命令:
$ 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
,该命令在之前保存哈希列表中查找并提取保存的行中的原始提交日期(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
当你对结果感到满意后,可以删除备份分支和用于存储原始提交日期的文件:
$ git branch -D old_master
$ rm /tmp/hashlist
就这些!
=
)。正确的命令是 git log --format="%aI %s" ...
。已更新答案。 - axiac%cI
与 git log
一起使用,但 "could not write rewritten commit" 部分看起来像是由 git filter-branch
产生的错误(而该命令不使用 %cI
)。 - axiac/c/.../commit-list
替换了相对路径,然后它就可以工作了。 :p - Zarepheth所以,以下是一种繁琐的方法(取决于需要变基的提交数量),但我已经尝试过它,它是有效的。当您进行交互式变基时,请使用“e”标记每个提交以便您可以编辑它。这将导致git在每个提交后暂停。在每个暂停时,您可以指定要使用的日期并继续下一个提交:
GIT_COMMITTER_DATE="Wed Feb 16 14:00 2011 +0100" git commit --amend
git rebase --continue
或者如果你希望保留提交者和作者的日期相同:
GIT_COMMITTER_DATE="Wed Feb 16 14:00 2011 +0100" git commit --amend --date "Wed Feb 16 14:00 2011 +0100"
git rebase --continue
--amend
后添加--no-edit
,如果你不想打开编辑器来更改提交内容。当然,这是个比较棘手的问题,你必须提前知道所有提交日期,但如果没有其他方式,至少应该能够起作用。git -c rebase.instructionFormat='%s%nexec GIT_COMMITTER_DATE="%cD" git commit --amend --no-edit' rebase -i