☰
Git修改提交时间全攻略:作者时间与提交者时间如何重写
2026/10/11 2:39:56 网站建设 项目流程

你如果在一个项目里折腾了挺久,突然发现自己的 Git 提交时间看着不对劲——要么是笔记本系统时间没同步,要么是换过时区、调过 BIOS,再或者纯粹是当时赶工没注意设置。等到推送之后,代码托管平台上那排提交记录显示的时间和你真实的开发时间对不上,后面核对版本或复盘的时候就会非常别扭。

这篇文章就是来解决这个问题的。我讲清楚 Git 里“提交时间”到底存在哪里,以及如何把一条、甚至一整个分支的提交时间改成你想要的样子。流程会覆盖从本地改时间、重写历史、再到推送远端的完整链路。适合有一定 Git 使用经验、但还没深入碰过重写历史操作的开发者。我会直接把命令、参数、格式和最实的坑都写出来,你照着操作就行。

1. “作者时间”和“提交者时间”从哪里来

1.1 为什么一个提交有两个时间戳

新手最容易混淆的一点:Git 提交不是只有一个时间。用git cat-file -p看任意一个提交对象,里面会包含author和committer两段信息,每一段都带着自己的时间戳。

这两者的意义完全不同:

  • “作者时间”(author date)表示这个改动“是什么时候写出来的”,偏向创作意图。
  • “提交者时间”(committer date)表示这个提交“是什么时候被放进当前仓库历史的”,偏向流程动作。

大多数时候两者一样,因为本地git commit会同时设置两者为当前系统时间。但只要发生 rebase、cherry-pick、reword 等重写操作,提交者时间就会变成操作发生的时刻,而作者时间保持不变。所以你看一些开源项目的提交历史,偶尔会发现某条提交的“作者时间”是很久以前写的,“提交时间”却是最近才合入的,这就是典型的重写历史结果。

1.2 怎样查看一个提交的完整时间信息

git log --oneline默认只看得到相对时间和简化信息。要看完整时间,我建议用fuller格式,它会同时列出作者时间和提交者时间:

git show -1 --format=fuller

输出大致长这样:

commit 8f6a2c9e31d6f1cc0d6b1b2c3d4e5f6a7b8c9d0e Author: your_name <your_email> AuthorDate: 2024-12-20 14:32:10 +0800 Commit: your_name <your_email> CommitDate: 2024-12-21 09:15:44 +0800

如果只需要列表,也可以用git log配合自定义占位符:

git log --format='%h %ad | %cd | %s' --date=iso-strict

%ad 是作者时间,%cd 是提交者时间。改完时间后,你就靠这些命令来确认结果,别只看托管平台上显示的排序,那不一定反映完整信息。

1.3 时间格式与时区为什么不能乱写

Git 能接受的时间格式比较宽松,但为了减少误解,我建议统一用 ISO 8601 格式,带上时区偏移量。例如:

2024-06-01T10:30:00+08:00

这表示“东八区的 2024年6月1日上午10点30分”。如果你不带时区只写2024-06-01 10:30:00,Git 会当作本地时区去解读。在不同机器上,这个“本地时区”很可能不一样,结果就会出现“我把时间改成下午三点,推上去怎么显示的是上午十一点”这种诡异问题。所以记住:只要改了时间,就写带时区的完整格式。

2. 只改最近一次提交时间:--amend 的正确打开方式

2.1 --amend 只改“最近一条”到底影响了什么

git commit --amend做的事情,是拿当前暂存区内容重新生成一个提交对象,用来替换HEAD指向的那个提交。因为你改了提交对象的内容(哪怕只改时间),新对象的哈希值必然会变。从效果上看,历史里 HEAD 这一条变了,但它的父提交没有变,所以影响范围被限定在“最近这一个提交”。

最关键的一点:如果这个提交从来没有推到过远端,那随便改,没有任何连带影响。如果已经推上去过,那改完后必须强制推送或重新推送,否则远端不认。这一点我会在第 4 章详细展开。

2.2 用 --date 参数和用环境变量的区别

网上最常见的写法是:

git commit --amend --date="2024-06-01 10:30:00 +0800" --no-edit

这个命令会把作者时间改成指定值。注意,--date参数并不修改提交者时间。如果你只改了作者时间,提交者时间仍然是刚才执行命令的当前时间。

那我们看看为什么很多教程会推荐环境变量的写法。下面这个命令可以同时修改作者时间和提交者时间:

GIT_AUTHOR_DATE="2024-06-01T10:30:00+08:00" \ GIT_COMMITTER_DATE="2024-06-01T10:30:00+08:00" \ git commit --amend --no-edit

--no-edit是告诉 Git:不要弹出提交信息编辑器,保留原来的 commit message 不变。这样整条命令执行完,提交信息、文件内容都没变,只有时间戳被换成指定值。

我个人的习惯是优先用环境变量版本。因为它“两手都抓”,不会出现作者时间改了、提交者时间还是现在的时间这种割裂状态。两边的日期一致,推到远端后展示也统一。

2.3 时间格式与时区的坑,怎么验证改对了

执行完上面的命令后,一定不要急着推送。先验证:

git log -1 --format='%h Author: %an | %ai%n Commit: %cn | %ci'

%ai和%ci可以强制使用严格 ISO 格式,一眼能看到时区。比如:

8f6a2c9 Author: your_name | 2024-06-01T10:30:00+08:00 Commit: your_name | 2024-06-01T10:30:00+08:00

这时候你再决定是否推送。如果只是本地历史,那就完成了。想推远端,看第 4 章。

3. 批量回改历史提交:interactive rebase + exec

3.1 rebase 如何对待时间,理解这一点很重要

在进入批量修改之前,先搞清楚一个背景:当你执行git rebase把若干提交重新应用一遍时,每个提交的作者时间会保留原值,但提交者时间会被重置为 rebase 执行时的当前时间。这是 Git 的正常机制,不是 bug。

所以如果你想批量“重放”旧提交并统一时间,可以借助 rebase 的交互模式,在重新应用某个提交之后、进入下一条提交之前,插入一条exec指令去执行git commit --amend,把时间改掉。

这里有一个容易搞混的点:exec执行的是 shell 命令,执行时当前 HEAD 正好指向刚才被重新应用出来的提交。所以你在exec中执行git commit --amend --no-edit,修改的恰好就是“当前这一个”提交,而不是别的提交。

3.2 通过 rebase 脚本精确指定每条提交的时间

假设你现在有 4 条提交在分支上,想修改倒数第 2 条和第 3 条的时间,其他不动。执行:

git rebase -i HEAD~4

编辑器会打开一个 rebase 脚本,大致内容如下:

pick 1111111 提交A:初始化 pick 2222222 提交B:增加功能 pick 3333333 提交C:修复问题 pick 4444444 提交D:补充文档

我想把 B 和 C 的时间都改成某个历史时间点,就编辑成下面这样:

pick 1111111 提交A:初始化 pick 2222222 提交B:增加功能 exec GIT_AUTHOR_DATE='2024-05-10T09:00:00+08:00' GIT_COMMITTER_DATE='2024-05-10T09:00:00+08:00' git commit --amend --no-edit pick 3333333 提交C:修复问题 exec GIT_AUTHOR_DATE='2024-05-11T16:20:00+08:00' GIT_COMMITTER_DATE='2024-05-11T16:20:00+08:00' git commit --amend --no-edit pick 4444444 提交D:补充文档

保存退出后,Git 依次执行:

  • 重新应用 A
  • 重新应用 B
  • 执行对应的 exec 命令,把 B 的时间改成 5 月 10 日
  • 重新应用 C
  • 再执行第二条 exec,把 C 的时间改成 5 月 11 日
  • 重新应用 D

整个过程结束后检查:

git log --format='%h %ad | %cd | %s' --date=iso-strict HEAD~4..HEAD

留意这里的一个细节:B 的时间改过之后,C 的提交对象里记录的上一个提交哈希值会变化,因为历史被重写了。这是预期中的连锁反应,不用慌。

3.3 处理根提交:--root 参数解决“最老一条改不动”的困境

有人会问:如果我想改动的是当前分支最老的那条初始提交,怎么办?普通git rebase -i HEAD~n并没有指向它之外的“更老的父提交”可以用,因为根提交(root commit)没有父提交。

解决办法是加--root:

git rebase -i --root

这样 rebase 范围会扩展到所有提交,包括最老的那条。在编辑脚本里同样可以插入exec来修改根提交的时间。注意一点:正常 rebase 中第一条提交通常是pick,它没有“前一条提交”作为 base,但--root模式下它一样可以被重放和修改。本质上你是在重建从第一条开始的整个链条,所以执行完要格外仔细检查。

4. 远端推送:force / force-with-lease 的选择与安全检查

4.1 为什么改完时间会推不上去

当你本地修改了提交时间,哪怕是只改一条,那条提交的哈希值也变了。对远端仓库来说,它记录的历史和你本地历史出现了分叉,服务器会拒绝普通的git push,提示类似“non-fast-forward”的信息,也就是远端觉得你本地落后,或者历史不一致,不允许直接覆盖。

如果这些提交只存在于一个全新的、没人用过的功能分支,远端本来就什么都没有,你直接推送没有任何问题,因为不存在分叉。问题只出现在“这条分支已经推送过”的情况下。

4.2 从 --force 到 --force-with-lease,差别在哪

很多人的第一反应是git push --force origin branch_name。它会无条件把本地历史覆盖到远端。但这其实有风险:如果在你改时间的这段时间里,同事往同一条分支上推了新的提交,你的--force会把他们刚推上去的内容也一并覆盖掉,而且很难找回。

更稳妥的做法是用--force-with-lease:

git push --force-with-lease origin branch_name

这个参数的意思是:只有当远端分支的最新状态和你本地缓存的远程跟踪分支一致时,才允许强制推送;如果远端在你这段时间内多了新提交,命令会被拒绝。这样能避免误伤别人的工作。

还有一种更严格的选择是--force-if-includes,它在--force-with-lease的基础上,额外检查远端新提交是否已经被你本地包含。在跨多设备协作、远端变化频繁的场景下更安全,但日常单人项目工期比较紧张时,--force-with-lease已经够用。

4.3 团队协作中,重写共享分支的后果要提前说清楚

这里想多说一点判断逻辑。修改提交时间本质上属于“重写历史”,不是普通的代码变更。对于无人知晓的本地提交,随意改。对于已经共享出去、别人可能拉取过的提交,每改一次,别人下次 pull 大概率会冲突或需要重新拉取新历史。

实际项目里我建议遵守两条原则:

  • 功能分支是自己在维护、还没有合入主干之前,重写历史可以接受,毕竟不会影响别人。
  • 主干或多人协作的核心分支,不要为了“时间美观”去重写历史。如果确实要改,先和团队同步,统一在一个时间窗口做完,然后所有相关的人重新 clone 或者按约定方式同步。

如果你的托管平台开启了分支保护,强制推送通常会被直接拦截,需要临时调整保护规则或走评审流程。这些都是平台的界面操作,不同平台细节不同,但逻辑一致——它本身就预设了“共享历史不应轻易重写”的规则。

4.4 修改历史之后,远端页面的“更新时间”也会跟着变

推送时间修改这个说法,在不同语境下有两种理解:一是“什么时候 push 上去”这个动作时间,二是“推送的提交本身时间戳”。这里必须说清楚:网络层面的 push 动作时间由服务器记录,本地怎么改都影响不到。

但代码托管平台的提交列表、文件变更记录、最近活跃时间,展示的都是提交对象里的时间戳。你把提交时间改了之后推送上去,平台展示的更新时间就会变成你设定的新值。这也是大多数人说“修改 Git 推送时间”真正想要的结果。所以,从提交到推送,本地改时间戳是唯一可控的路径,网络传输时间本身不必也不能改。

4.5 推送后的验证步骤

推送完成后,不要只在 GitHub 网页上瞄一眼排序就觉得搞定。稳妥的办法是在另一台机器上拉取,或者用一个独立的本地目录验证:

git fetch origin branch_name git log --format='%h %ad | %cd | %s' --date=iso-strict FETCH_HEAD..origin/branch_name

要看的是远端分支上的提交对象时间是否符合预期。如果远端显示的时间还是旧的,说明你刚才推送的可能不是最终修改过的那条分支,或者本地还有未提交的改动把时间覆盖了。多一步验证不费事,能省掉后面误判的麻烦。

5. 我在实际操作里反复踩到的三个坑

5.1 坑一:时间字符串没带时区,改完时间“漂移”

这个坑我踩过不止一次。有次同事要给某个提交设置一个统一的日期,直接写了GIT_AUTHOR_DATE="2024-06-01 10:30:00",不带任何时区。本地测试看着没问题,推上去之后,托管平台显示的是 08:30 或者别的时区换算结果,因为他那台机器和查看页面的浏览器不在同一个时区。

解决方式非常简单:所有时间都写+08:00或+00:00这种带偏移的字符串。别嫌麻烦,这是唯一能保证“我写的是什么,别人看到就是什么”的写法。

5.2 坑二:批量 rebase 时,exec 行的环境变量互相“串台”

有些朋友喜欢在 rebase 脚本里先定义一个变量,然后多条 exec 共用:

pick 2222222 提交B exec TIMESTAMP='2024-05-10T09:00:00+08:00'; export GIT_AUTHOR_DATE="$TIMESTAMP"; export GIT_COMMITTER_DATE="$TIMESTAMP"; git commit --amend --no-edit pick 3333333 提交C exec TIMESTAMP='2024-05-11T16:20:00+08:00'; export GIT_AUTHOR_DATE="$TIMESTAMP"; export GIT_COMMITTER_DATE="$TIMESTAMP"; git commit --amend --no-edit

这种方式语法上可行,但阅读和排错都很痛苦。尤其是你复制粘贴、半路改了一个时间,另一条却忘了改,最终结果就是两条提交时间一模一样。我的建议是每条 exec 写成独立完整的一行,不做变量复用:

exec GIT_AUTHOR_DATE='2024-05-10T09:00:00+08:00' GIT_COMMITTER_DATE='2024-05-10T09:00:00+08:00' git commit --amend --no-edit

一行一个提交,一目了然。

5.3 坑三:签名提交和 hooks 可能让“只改时间”变成“多改内容”

如果这个仓库启用了 GPG 提交签名,哪怕你只是改时间,新的提交对象也会产生新的哈希,签名内容必须重新计算。在exec里执行git commit --amend --no-edit时,Git 会尝试重新签名,但如果环境里支持 GPT 状态的配置不一致,或者你换了机器、没带对应的密钥,命令可能失败。

另外,如果你配置了commit-msghooks 或者路径检查脚本,amend 的时候这些 hook 也会再次执行。好在--no-edit不会改变提交信息,大多数 hook 不会因此报警,但特殊情况因人而异。我的习惯是:改动前先确认仓库是否配置了 hooks 和签名,避免改到一半报错,还要处理一个已经“半重写”的历史状态。

5.4 保留一张“后悔药”:重写前先留恢复锚点

重写历史最原始的风险是:手滑改坏了,原历史找不到。尤其是多分支、多提交的批量修改,一旦 rebase 执行过程中断,恢复起来非常麻烦。

我建议你在执行任何会比上次一次提交修改时间更复杂的操作之前,先做一个轻量备份。最简单的方式是记下当前分支位置并创建临时标签:

git branch backup-timeline-before-rewrite git log --oneline -1

如果操作后不满意,可以用:

git reset --hard backup-timeline-before-rewrite

完整回到操作前状态。这个临时分支用完再删掉即可。比用 reflog 翻找更直观,也不怕 reflog 过期丢失记录。

我在实际修改提交时间时也遇到过意想不到的问题,比如先改了本地提交时间,又隔了几天其他分支基于旧历史开了新分支,结果推远端时被拒绝。这种情况下不要慌,回到这条分支上重新确认基础提交即可。说到底,修改提交时间本身不难,真正考验人的是重写历史之后的推送策略和团队协作边界。明白每个命令改了什么、影响范围多大,你就不会在这些细节上再吃亏。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询