不改变提交时间戳的情况下进行git rebase

227

在保留提交时间戳的同时执行git rebase是否有意义?

我相信一个后果是,新分支不一定按照提交日期排序。这是否理论上可能?(例如使用管道命令; 我只是好奇)

如果在实践中可以进行,则是否可以使用rebase而不更改时间戳?

例如,假设我有以下树:

master <jun 2010>
  |
  :
  :
  :     oldbranch <feb 1984>
  :     /
oldcommit <jan 1984>

现在,如果我将oldbranchmaster上进行变基操作,则该提交的日期从1984年2月更改为2010年6月。是否可能更改此行为,以便提交的时间戳不更改?最终我将获得:

      oldbranch <feb 1984>
      /
 master <jun 2010>
    |
    :

这样做有意义吗?在Git中是否允许存在一个历史记录,其中旧提交具有更近期提交作为父提交?


3
答案有些奇怪,实际上是“你不需要做任何事情 - 这是默认的工作方式”。但是现在假设你想在执行变基时按正确的日期顺序对提交进行排序(如果你考虑一下,这是很自然的场景)。我找不到如何实现这一点,并发布了我的问题,链接为https://dev59.com/eGct5IYBdhLWcg3wL6ui。 - pfalcon
3
David提到了另一个重置提交者日期的选项: git rebase --committer-date-is-author-date SHA。请参阅我下面编辑的答案 - VonC
我刚刚在一个类似的问题上写了一个详细的回答,该问题的作者尝试了这里解释的答案,但不能令他满意地应用它们。 - axiac
7个回答

237

2014年6月更新:David Fraser评论中提到了一种解决方案,也在“更改重新基础git分支的时间戳”中进行了详细介绍,使用选项--committer-date-is-author-date(最初在提交3f01ad6中引入于2009年1月)。

Note that the --committer-date-is-author-date option seems to leave the author timestamp, and set the committer timestamp to be the same as the original author timestamp, which is what the OP Olivier Verdier wanted.

I found the last commit with the correct date and did:

git rebase --committer-date-is-author-date SHA

参见 git am

--committer-date-is-author-date

By default the command records the date from the e-mail message as the commit author date, and uses the time of commit creation as the committer date.
This allows the user to lie about the committer date by using the same value as the author date.

注意:从 Git 2.29 (2020 年第四季度) 开始,git rebase --committer-date-is-author-date--ignore-date 也适用于以下情况:
  • 交互式 rebase (rebase -i/rebase --interactive)
  • 对于根提交 (git rebase --root)
请参见 "在 rebase git 分支时更改时间戳"。

(原始回答,2012年6月)

你可以尝试使用非交互式的变基
(请参见上文:使用Git 2.29,2020年第4季度,这也适用于交互式变基)

git rebase --ignore-date

(来自这个SO答案

这将传递给git am,它提到:

 --ignore-date

默认情况下,该命令将电子邮件中的日期记录为提交作者日期,并使用提交创建时间作为提交者日期。
这允许用户通过使用与提交者日期相同的值来欺骗作者日期。
对于 git rebase,此选项与“--interactive”选项不兼容。
由于 您可以随意更改旧提交日期的时间戳(使用git filter-branch),我想您可以根据需要以任何提交日期顺序组织您的Git历史记录,甚至将其设置为未来!

正如Olivier在他的问题中提到的那样,作者日期永远不会因为变基而改变;
来自Pro Git Book

  • 作者是最初撰写该作品的人,
  • 而提交者是最后应用该作品的人。

因此,如果您向项目发送补丁,并且核心成员之一应用了该补丁,则两个人都会获得信用。

为了更加清晰,在此情况下,正如Olivier所评论的:

--ignore-date的作用与我想要实现的相反
也就是说,它会删除作者的时间戳并用提交的时间戳替换它们!
因此,对我的问题的正确答案是:
不要做任何事情,因为git rebase默认情况下实际上不会更改作者的时间戳。


正如DylanYoung评论中所述,使用"如何在Git rebase期间通过哈希标识冲突提交?":

Using the SEQUENCE_EDITOR variable and rebase interactive, you would just loop over the current todo list and add a command setting the GIT_COMMITER_DATE to the date of the original commit before each commit in the todo.

It's a bit less fiddly because you have the list of original commits to start with (you don't have to hack into git internals to find it), but a bit more work because you have to handle the entire list at once. –

Once you're able to identify the original commit you can do something like:

git rebase -x 'GIT_COMMITTER_DATE="git show -s --format=%ci ``get_current_commit``" git commit --amend --no-edit

2
关于提交任意日期的想法很有趣。然而,git rebase --ignore-date 并不起作用。它会改变被合并提交的日期。 - Olivier Verdier
2
谢谢VonC,作者和提交者时间戳之间的区别让一切都变得清晰起来。我在我的帖子中回答了我的问题,但请随意调整您的答案以反映这一点。 - Olivier Verdier
5
更准确地说,--ignore-date 的作用与我试图实现的相反!它会擦除作者的时间戳并用提交的时间戳替换它们!因此,我的问题的正确答案是:不要做任何事情,因为默认情况下 git rebase 实际上不会更改作者的时间戳。 - Olivier Verdier
10
请注意,--committer-date-is-author-date选项似乎会保留作者的时间戳,并将提交者的时间戳设置为与原始作者时间戳相同,这正是Olivier想要的... - David Fraser
1
在所有分支上运行 git rebase --committer-date-is-author-date SHA_of_root_commit,将它们全部压缩在一起!太棒了! - endolith
显示剩余15条评论

141

如果您已经通过rebase等方式弄乱了提交日期,并希望将它们重置为其对应的作者日期,可以运行以下命令:

git filter-branch --env-filter 'GIT_COMMITTER_DATE=$GIT_AUTHOR_DATE; export GIT_COMMITTER_DATE'


2
我刚试了一下,但没有效果。我得到了以下输出:WARNING: Ref 'refs/heads/master' is unchanged。我在Linux(64位)上使用的是git版本1.7.9.5。 - Markus N.
33
如果你已经搞砸了但不想遍历整个历史记录,我想添加另一种方法: git rebase --committer-date-is-author-date <base_branch> 这样,Git 将仅重置应用于 <base_branch> 上的提交的提交日期(这很可能是你在搞砸时使用的相同分支名称)。 - speakman
1
被接受的答案在2016年无效,但@speakman的答案有效! - Theodore R. Smith
2
@speakman的答案在2016年10月份并没有起作用,但Andy的方法可以用! - Amedee Van Gasse
2
这在Windows上不起作用。我能够使用Windows Bash让它工作。 - vaindil
截至2017/07/31,https://dev59.com/v3A85IYBdhLWcg3wCe5Z#11179245仍然有效,但是https://dev59.com/v3A85IYBdhLWcg3wCe5Z#s6mfEYcBWogLw_1buiP8无效。Git由于其Unix根源,往往选择非人类导向的默认设置,但幸运的是,它足够强大,可以在需要时撤消它们。 - Zack Morris

48

Von C 的一个重要问题帮助我理解了正在发生的事情:当您进行变基时,提交者的时间戳会更改,但作者的时间戳不会更改,这突然间就很清楚了。因此,我的问题实际上不够精确。

答案是,变基实际上不会更改作者的时间戳(您不需要做任何操作),这非常适合我。


4
我已经设置了一个 Git 别名(https://coderwall.com/p/euwpig/a-better-git-log),它似乎使用提交者的时间戳,这让我感到困惑。Gitk 和 git log 都显示作者的时间戳。 - 1615903

22

默认情况下,git rebase 命令会将新提交的时间戳设置为提交者提交的时间,但保持作者的时间戳不变。大多数情况下,这是期望的行为,但在某些情况下,我们不希望更改提交者的时间戳。那么,我们该如何实现呢?我通常会采用以下技巧。

首先,请确保您将要合并的每个提交都有唯一的提交信息和作者时间戳(这是需要改进的地方,目前它符合我的需要)。

在合并之前,将所有将被合并的提交的提交者时间戳、作者时间戳和提交信息记录到一个文件中。

#NOTE: BASE is the commit where your rebase begins
git log --pretty='%ct %at %s' BASE..HEAD > hashlog

然后,进行实际的变基操作。

最后,如果提交信息相同,我们使用 git filter-branch 将当前提交者的时间戳替换为文件中记录的时间戳。

 git filter-branch --env-filter '__date=$(__log=$(git log -1 --pretty="%at %s" $GIT_COMMIT); grep -m 1 "$__log" ../../hashlog | cut -d" " -f1); test -n "$__date" && export GIT_COMMITTER_DATE=$__date || cat'

如果出现了问题,只需检查 git reflog 或所有的 refs/original/ 引用。

此外,您也可以对作者的时间戳进行类似的操作。

例如,如果某些提交的作者时间戳顺序不正确,并且不希望重新排列这些提交,只想让作者的时间戳按顺序显示,则可以使用以下命令。

git log --pretty='%at %s' COMMIT1..COMMIT2 > hashlog
join -1 1 -2 1 <(cat hashlog | cut -f 1 | sort -nr | awk '{ print NR" "$1 }') <(cat hashlog | awk '{ print NR" "$0 }') | cut -d" " -f2,4- > hashlog_
mv hashlog_ hashlog
git filter-branch --env-filter '__date=$(__log=$(git log -1 --pretty="%s" $GIT_COMMIT); grep -m 1 "$__log" ../../hashlog | cut -d" " -f1); test -n "$__date" && export GIT_AUTHOR_DATE=$__date || cat'

这是一个很棒的技巧!它让我只需要重写75个提交,而不是使用其他答案时需要重写1100多个。 - audun
这太棒了!有没有办法修改脚本,同时也保留原始提交者? - David DeMar
@DavidDeMar 应该是一样的,只需要将 git log --pretty 更改为记录原始电子邮件,并相应修改脚本即可。 - weynhamz

6

看起来这个真正的解决方案来自于Reddit。稍微改进一下,就是这样:

git -c rebase.instructionFormat='%s%nexec GIT_COMMITTER_DATE="%cD" git commit --amend --no-edit --allow-empty --allow-empty-message' rebase -i

1
请更详细地解释为什么这被认为是“真正”的解决方案。 - dfsg76
1
@dfsg76 因为它完全按照要求执行,没有任何问题。其他答案要么将提交时间重置为作者时间(?!),要么需要在存储库中添加后重写钩子,或者在存在重复日期和提交消息时失败。 - user541686

3

post-rewrite 钩子

该钩子适用于所有的 git rebase, git pull --rebasegit commit --amend 操作。

.git/hooks/post-rewrite

set -eu
echo post-rewrite
if [ ! "${CIROSANTILLI_GITHOOKS_DISABLE:-0}" = 1 ]; then
  declare -a olds
  declare -A oldnew
  while IFS= read -r line; do
    echo "$line"
    old="$(echo "$line" | cut -d ' ' -f1)"
    new="$(echo "$line" | cut -d ' ' -f2)"
    oldnew[$old]="$new"
    olds+=("$old")
    news+=("$new")
  done
  git reset --hard "${news[0]}~"
  for old in "${olds[@]}"; do
    new="${oldnew[$old]}"
    git cherry-pick "$new" &>/dev/null
    olddate="$(git log --format='%cd' -n 1 "$old")"
    CIROSANTILLI_GITHOOKS_DISABLE=1 \
      GIT_COMMITTER_DATE="$olddate" \
      git commit \
      --amend \
      --no-edit \
      --no-verify \
      &>/dev/null \
    ;
  done
  echo
fi

GitHub 官方仓库

别忘了:

chmod +x .git/hooks/post-rewrite

在选择的存储库上默认执行--committer-date-is-author-date是一个很好的方法,在有人最终修补配置以默认设置之前。

它也适用于--committer-date-is-author-date,这似乎不会暴露在git pull --rebase中。

另请参阅:

在git 2.19,Ubuntu 18.04上测试通过。


1
非常有趣。+1 - VonC
"declare -A" 报错并显示以下选项:"declare: usage: declare [-afFirtx] [-p] [name[=value] ...]"。 - soMuchToLearnAndShare
@soMuchToLearnAndShare,你的Bash/操作系统版本是什么?在Ubuntu 22.04上仍然存在Bash 5.1。 - Ciro Santilli OurBigBook.com

0

这是我在我的情况下使用的命令:

GIT_AUTHOR_EMAIL=xaionaro@dx.center git rebase --root -x "bash -c 'git commit --amend --reset-author -CHEAD --date=\"\$(git show --format=%ad -s)\"'"

在这里,git show --format=%ad -s提取当前日期,--date强制执行它。而rebase --root -x为每个提交执行命令bash -c 'git commit --amend --reset-author -CHEAD --date="$(git show --format=%ad -s)"'

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