【问题标题】:How to make a git rebase and keep the commit timestamp?如何制作 git rebase 并保持提交时间戳?
【发布时间】:2015-08-27 17:31:37
【问题描述】:

我想进行变基以从我的历史记录中删除某个提交。我知道该怎么做。但是,如果我这样做,提交时间戳将设置为我完成 rebase 的那一刻。我希望提交保持时间戳。

我在这里看到了最后一个答案:https://stackoverflow.com/a/19522951/3995351,但是没有用。

最后一个重要的命令刚刚显示了一个新行

>

所以我要提出一个新问题。

【问题讨论】:

  • “没用”,“答案”...请链接到答案,也许更深入地解释一下..
  • FWIW,默认情况下应保留 作者 日期。更改的只是 commit 日期。
  • 您是否也尝试过该问题的其他答案?给我们更多信息
  • @Vogel612 问题是关于作者日期的问题。我不想改变的是提交日期。我指的答案是唯一符合我需求的答案
  • @DavidDeutsch 是的,我希望提交日期不要更改

标签: git timestamp commit git-rebase


【解决方案1】:

所以,这是一种乏味的方法(取决于您需要重新设置多少次提交),但我尝试了它并且它有效。当您进行交互式变基时,请用“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

当然,这是一个很大的麻烦,你必须事先知道所有的提交日期,但如果你不能以任何其他方式做到这一点,它至少应该可以工作。

【讨论】:

  • 嗯,我有大约 300 次提交......所以手工完成这个应该非常困难,但也许我们可以自动化这个命令集?
  • 哇,300 对我的方法来说绝对是太多了,除非你手头有很多时间。我唯一可以建议的另一件事是查看this questionlast 答案。
  • 啊,对不起。
  • 也许你可以帮助我解决这个问题,因为那个答案对我来说失败了..
【解决方案2】:

设置

假设这是您要删除的提交的历史记录

... o - o - o - o ...       ... o
        ^   ^   ^               ^
        |   |   +- next         |
        |   +- bad              +-- master (HEAD)
      start

地点:

  • bad 是您要删除的提交;
  • start 是您要删除的提交的父级;
  • nextbad 之后的下一个提交;很好,你想保留它和它之后的所有时间线;它将在 rebase 后替换 bad

先决条件

为了能够安全地删除bad,重要的是在bad 创建时没有其他分支被合并到bad 之后的主时间线中。 IE。通过从历史图表中删除 bad 及其与其父提交和子提交的连接,您将获得两个断开连接的时间线片段。

即使在bad 之后合并了另一个现有分支,也可能删除bad。我没有检查这种情况,但由于合并提交,我预计会遇到一些障碍。

想法

每个git 提交都由使用提交属性计算的哈希标识:内容、消息、作者和提交者日期和电子邮件。

变基总是改变提交者的日期。它还可以更改提交者电子邮件、提交消息和内容。

为了在变基后恢复原始提交者日期,我们需要将它们与一些可以识别变基后的每次提交的信息一起保存。

因为要修改一个提交,所以在 rebase 期间提交内容会发生变化。添加或删除文件或提交会更改所有未来提交的内容。

这使我们没有唯一标识提交并且在所需的变基期间不会更改的属性。我们可以尝试使用两个或多个在 rebase 期间不会更改的属性。

电子邮件(作者和提交者)几乎没有用。如果有一个人在该项目上工作,则它们对于所有提交都是相同的,并且不能使用。剩下的属性(在大多数提交上不同,不受变基的影响)是 author datecommit message(第一行)。

如果这对(作者日期,提交消息)为受 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 开头的提交的提交哈希、提交者日期(有效负载)、作者日期和提交消息(密钥),并将它们存储在一个文件中。

备份当前master

虽然git“重写历史”是一个常见的误解,但实际上它只是生成了一条替代历史线并确定它是正确的历史。它不会更改或删除“重写”的提交;它们在其数据库中仍然存在一段时间,并且可以在操作失败时恢复。

我们可以主动备份当前的历史记录行,以便在需要时轻松恢复。我们所要做的就是创建一个指向master 的新分支。这样,当git rebasemaster 移动到新时间线时,仍然可以使用新分支访问旧时间线。

$ git branch old_master

上面的命令创建了一个名为 old_master 的分支,它保持当前时间线为焦点,直到我们完成所有更改并对新的世界秩序感到满意。

做rebase

从历史记录中删除提交 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 遍历标记为startmaster 的提交之间的历史记录,并且对于每个提交,它在重写提交之前运行作为--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

清理

当您对结果感到满意时,您可以删除备份分支和用于存储原始提交日期的文件:

$ git branch -D old_master
$ rm /tmp/hashlist

就是这样!

【讨论】:

  • 非常感谢!看起来很有希望:) 今天会尝试。
  • 第一个命令给我一个错误: git log --format "%aI %s" c318373c22214f91fb189619e093be298aa6c0e0...lollipop |独特 | wc -l 致命:不明确的参数“%aI %s”:未知修订版或路径不在工作树中。使用 '--' 将路径与修订分开,如下所示:'git [...] -- [...]'
  • 缺少等号 (=)。正确的命令是git log --format="%aI %s" ...。更新了答案。
  • 嘿,一切正常,但最后一步它说:(1/219)fatal: invalid date format: %cI could not write rewritten commit
  • 最终我用来自根/c/.../commit-list 的绝对路径替换了相对路径,它起作用了。 :p
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-07-15
  • 2018-12-26
  • 2012-06-05
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多