Git变基:保留最新提交日期

10
我一直在开发一个功能分支,现在已经准备好推送到远程仓库了。它有5个左右的提交,其中有些是非常快速的提交,不值得单独作为一个提交。我希望在将其推送到远程进行审查之前,将该功能合并成一个提交。
通常我会执行以下操作:
git rebase -i HEAD~5
然后将这些提交合并到前一个提交中。这样会给我一个我想要的提交,但是,提交日期是第一个提交的日期。我想要的是一个提交,其提交日期为最后一个提交的日期。
如何将这些提交合并到最后一个提交中,而不是第一个提交中?

我可以想象为什么他们这样做。对于修复提交,原始数据是正确的,你肯定想保留原始作者。对于压缩提交,不太清楚是否需要这样做。像如果超过30%的代码来自第二个提交,则采用新日期等复杂行为,等等,不值得实现的努力。 - Joshua Clayton
4个回答

6
你可以在任何新提交中设置任意日期。
当你使用rebase -i将提交合并在一起时,你实际上并没有“更改”旧的提交,而是创建了一个新的提交,在此之后你只需停止使用旧的提交:
(before rebase -i)

A--B--C--D--E--F--G--H--I   <-- master

(after rebase -i)

           E--F--G--H--I   [abandoned]
          /
A--B--C--D--J   <-- master

其中J是新的“所有事物合并”的提交。

坏消息是,git rebase -i旨在保留其他提交被squashfixup到其中的提交的作者日期戳。因此它已经使用“设置任意日期”功能来强制J具有E的日期。交互式rebase命令没有标志可以更改这个。1

但是有一个简单的解决方法:在将新提交J作为E-F-G-H-I的新版和改进版后,您可以使用相同的一般想法,放弃J而选择更新更好的K,它与J完全相同,只是具有您所需的日期。

要执行此操作,请在重新基础之后运行git commit --amend --date=...(提供您喜欢的任何日期作为...部分)。您也可以更改作者;请参见git commit文档。这将创建新的提交K以替换J,就像J替换了E-F-G-H-I一样,使您拥有:

           E--F--G--H--I   [abandoned]
          /
A--B--C--D--K   <-- master
          \
           J   [abandoned]

您已经放弃的提交,如果没有其他方法让您找到它们,在大约30天后,将最终失去寻找它们的最后方法 - 它们的ID在reflogs中保留一段过期时间以防您想要它们回来 - 一旦它们真正成为未引用的内容,垃圾收集器(由git gc --auto运行,这又是由各种其他Git命令运行的)将真正删除它们。


1非交互式的基于git amgit rebase有一个标志,但没有合并功能。


3

git rebase -i SHA

执行rebase后有3个可用选项,选择其中任意一个。选项1仅适用于最近的提交。选项2和3无法与交互式rebase一起使用,因此需要单独调用rebase命令,否则会与上面的rebase调用合并。

  1. git commit --amend --no-edit --date=now
  2. git rebase --ignore-date SHA
  3. git rebase --committer-date--is-author-date SHA

在您的情况下,您可以在rebase之前找到最新的提交日期,并通过选项1在rebase之后提供该日期。如果您不知道要使用的日期格式,请尝试echo $(date)

(来自此SO答案:答案1答案2


2

这里有一种(相当酷的)另一种实现方式:

选择您要复制的最高提交以制作空白副本:

git cherry-pick --keep-redundant-commits HEAD

现在可以像以前一样进行交互式变基,可以指定精确的提交ID或在上一个父级计数上加1:
git rebase -i HEAD~6

现在将最后一次提交移动到列表顶部(为了方便,Git将注明提交为空)。选择顶部提交,并将其下面的所有提交更改为“f”以进行“修复”。保存并退出即可完成。

实际上,您正在执行相同的压缩操作,但是使用最后一个提交的作者、日期和消息而不是第一个。


1
这是我认为最好的解决方案。 - hochl

1
@torek的回答非常详细、全面且优秀。让我补充三个实用的命令行选项,以便您可以轻松复制和粘贴,并附上一些背景信息。
Git存储库保留两个时间信息:作者时间和提交时间。您想要修改的是最后一个提交(或当前分支中的HEAD)的作者时间,而不是提交时间(我认为修改后者很困难,无论如何,在大多数情况下您也不需要它)。
使用@torek提供的git历史示意图的示例,紧接在git rebase -i HEAD~5之后,
           E--F--G--H--I   [abandoned]
          /
A--B--C--D--J   <-- master

提交J是当前的head,它具有最新的提交时间(比任何其他提交都更新)和与提交E的作者时间相同。

现在,你需要做的是创建另一个提交K,并选择一个自己喜欢的作者时间,这将成为新的HEAD(详见@torek的答案)。你可以通过运行git commit --amend --date=XXX来实现,但是你选择的作者时间"XXX"是什么?

我可以想到三个最常见的选项:

  1. If you don't mind a slight difference in time and are fine with just the current time,

      git commit --amend --no-edit --date=now
    
  2. If your choice is the last amended time (commit-time), do either of (they are just aliases)

      git rebase --ignore-date HEAD
      git rebase --committer-date-is-author-date HEAD
    
  3. If your choice is the commit-time (and author-time) of your last git commit, i.e., commit I with commit-ID of SHA_I

      git commit --amend --no-edit --date=`git log -n 1 --format=%ct SHA_I`
    
请注意,在执行 git commit 命令时,应该已经打印出了(7 个字符的)字符串 SHA_I,它代表着所创建的提交 I。 或者,您可以使用命令 git reflog 显示提交列表。
如果您想在修改之前确认提交日期,请运行命令 git log -n 1 --format=%ci [SHA_I] (或者使用 %cd),如需查看作者日期,请将 %ci 替换为 %ai 等。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接