ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

Git提交时间修改全攻略:作者时间与提交者时间详解

Git提交时间修改全攻略:作者时间与提交者时间详解 Git 推送时间修改这几个词拆开都很好懂组合起来却经常让人绕晕。我在帮团队整理仓库历史时被问过最多的问题就是本地改了提交时间推到远端以后页面上的时间要么没变要么变得不对到底该怎么改才能让“推送过去的时间”正好是想要的这篇文章会从 Git 提交里两个时间戳的区别讲起把提交时间怎么改、改完怎么推、推完之后页面显示什么以及怎么用钩子和定时任务把推送动作固定到指定时刻整条链路一次讲透。适合正在整理提交历史、想统一仓库时间线或者想把补提的 commit 放回正确时间点的人。1. 先说清楚你要改的“推送时间”到底是哪个时间1.1 一个提交有两个时间戳Git 里的每个 commit 并不是只带一个时间而是带两个一个是 author date作者时间一个是 committer date提交者时间。作者时间表示这个改动是谁写的、什么时候写的。提交者时间表示这个 commit 对象是什么时候被记录进仓库的。大多数情况下两个时间是一样的因为你自己写完代码紧接着在当前机器上执行git commit两个时间都是当下。但一旦涉及补提、整理历史、跨机器操作它们就会分道扬镳。我习惯用一个类比来理解就像你花了一周写完一篇稿子文档创建时间是周一但周五才按下“保存并提交给编辑”的按钮。Git 会把“作者时间”记成你最初设定的时间同时把“提交者时间”记成周五那个实际入库的时刻。拓展到协作场景也一样——维护者用git commit --amend改了你的提交authored 还是你committed 已经是维护者时间也变成了他改的那一瞬间。和这个机制直接相关的是两个环境变量GIT_AUTHOR_DATE和GIT_COMMITTER_DATE。想改哪一个就设置哪一个。后面所有的操作归根结底都是在设置这两个变量。1.2 本地命令和远程页面分别看哪个时间很多人改错时间是因为没搞清楚自己眼睛看到的到底是哪个时间。本地执行git log默认显示的 Date 是作者时间。想看全两个时间可以用git log --formatfuller或者精确只看一个提交git show --formatfuller HEAD远程托管平台的逻辑不太统一。像 GitHub、GitLab、Gitee 在提交列表页、提交详情页里展示的日期有的取作者时间有的按提交者时间排序还有的干脆把“仓库最近更新时间”做成服务端收到推送的时刻。所以最稳妥的做法是两个时间一起改别只改其中一个。否则会出现一个很典型的现象本地git log看的是对的推到远端之后页面上显示的还是当前时间。原因往往就是作者时间改了提交者时间没改平台按后者的排序或显示逻辑把你“纠正”回去了。2. 改最近一次提交的时间三条命令足够2.1 只想改 author date 的场景如果你只是想修正最近一条提交的作者时间git commit --amend自带的--date参数就够了git commit --amend --no-edit --date2024-01-15 10:30:00这个操作会把当前 HEAD 这个提交的作者时间改成 2024 年 1 月 15 日上午十点半。--no-edit表示保留原来的提交说明不弹出编辑器。--date只动 author date提交者时间还是当前时刻这个特性在 1.1 节里已经说过注意区分。这里要提醒一下执行--amend本身会生成一个新的 commit 对象也就是说这个提交的 hash 已经变了。如果这个 commit 之前已经推到过远端那么这次修改意味着后面必须走强制推送牵扯到分支保护、协作者同步等一系列问题具体见第 5 节。2.2 把两个时间戳一起改作者时间和提交者时间一起改就不能只靠--date了需要显式设置两个环境变量GIT_COMMITTER_DATE2024-01-15 10:30:00 0800 \ GIT_AUTHOR_DATE2024-01-15 10:30:00 0800 \ git commit --amend --no-edit这里加了一个0800的时区后缀。为什么强调这个因为 Git 解析日期字符串时如果不带时区默认按当前机器的本地时区处理。如果本地是 UTC你写“10:30:00”推到东八区的仓库里看就会变成“18:30:00”差出来的 8 小时往往让你以为命令没生效。如果想顺手改作者身份可以加上--authorName email。配合环境变量作者、邮箱、作者时间、提交者时间就都能一次性重写得干干净净。这也是整理历史提交时的标准动作。2.3 时间字符串怎么写踩坑更少Git 能识别的时间格式不少但实际使用中我推荐下面几种不容易出歧义带时区的完整时间2024-01-15 10:30:00 0800ISO 8601 格式2024-01-15T10:30:0008:00Unix 时间戳1705285800前面加RFC 2822 格式Mon, 15 Jan 2024 10:30:00 0800如果你想把一个现成时间转换成 Unix 时间戳用date -d 2024-01-15 10:30:00 0800 %s输出结果类似1705285800然后就可以在命令里写--date1705285800。我的习惯是只要仓库可能会被不同时区的人看一律写带时区的完整格式。别偷懒省掉0800这正好是很多人踩过坑以后才养成的习惯。3. 改历史提交的时间rebase 与 filter-branch 的取舍3.1 用 interactive rebase 精确改某几个提交当需要改的不是最近一条而是历史里某几条提交时最可控的方案是交互式 rebase。假设你的分支上有 5 个提交要改倒数第 3 个的时间git rebase -i HEAD~5编辑器打开后你会看到类似这样的 todo 列表pick 1a2b3c4 提交1 pick 5d6e7f8 提交2 pick 9a0b1c2 提交3 pick e3f4a5b 提交4 pick c6d7e8f 提交5把要改的那一行前面的pick改成edit保存退出。Git 会在这个提交处停下来此时执行GIT_COMMITTER_DATE2024-01-15 10:30:00 0800 \ GIT_AUTHOR_DATE2024-01-15 10:30:00 0800 \ git commit --amend --no-edit git rebase --continueGit 会继续重放后面的提交直到 rebase 完成。这种方式的优点是精准你想停在哪一个提交上改就在哪一行标edit不会误伤其他提交。缺点也很明显如果待改的提交很多你得一条一条停、一条一条改效率不高。另外一个必须知道的坑rebase 会把你 edit 的那个提交以及它之后所有提交的 committer date全部重写成当前时间。因为 rebase 本质上是基于原提交内容重新生成一批全新的 commit 对象。所以即使用户没显式修改时间重放出来的提交的 hash 和提交者时间都变了。这也是为什么 rebase 之后git log --formatfuller里会出现一大片“当前时间”的原因。3.2 用 filter-branch 一次性改所有历史如果你想批量把很多提交甚至全部分支的时间统一改成某一个值用filter-branch的--env-filter会快很多git filter-branch --env-filter export GIT_AUTHOR_DATE2024-01-15 10:30:00 0800 export GIT_COMMITTER_DATE2024-01-15 10:30:00 0800 -- --all这段命令会在每个提交被重写前设置上面两个环境变量相当于把仓库里所有提交的作者时间和提交者时间都替换成同一时刻。-- --all表示作用于所有分支。需要明确提示的是Git 官方已经把 filter-branch 标记为 deprecated在大型仓库上它出名的慢而且容易遇到路径或内存问题。社区推荐的替代是git filter-repo它默认不随 Git 发行需要自己安装比如 pip 安装命令类似git filter-repo --commit-callback commit.author_date b2024-01-15 10:30:00 0800; commit.committer_date b2024-01-15 10:30:00 0800filter-repo 的功能更强但新用户上手成本也更高一点。我的建议是仓库不大、学校或小项目里用 filter-branch 可以接受一旦上了生产、历史成百上千条优先走 filter-repo且务必先在本地克隆副本上试跑一遍。3.3 改历史前必须想清楚的连锁反应改历史提交不是本地自嗨它带来的是整条提交链的 hash 变化。所有参与分支合并、pull request、review 的人都会在同步时遇到历史不一致。重写之后本地分支和远程分支的关系会彻底错位你只能强制推送覆盖远程分支git push --force-with-lease origin your-branch--force-with-lease比--force安全推送前会做一次远端引用比对避免误覆盖别人刚推上来的提交。团队里如果这个分支有其他人协作最好先微信或群里吼一声确认没人正在这个分支上干活再执行强制推送。还有一点很容易忽略filter-branch 会在refs/original/下保留一份原始引用备份。改完并确认无误后最好把备份和过期对象清理掉否则仓库会残留大量“幽灵对象”体积迟迟降不下来git for-each-ref --format%(refname) refs/original/ | xargs -n1 git update-ref -d git reflog expire --expirenow --all git gc --prunenow4. 把“推送时间”固定住钩子、别名与定时任务4.1 pre-push 钩子不合规时间直接拒绝修改时间只解决“一次性”问题。如果团队里有统一时间规范比如所有提交的 committer date 必须是当天或者不允许未来时间那么靠自觉不够最好在pre-push钩子里加一道闸门。在.git/hooks/pre-push放置下面的脚本并加上可执行权限#!/bin/sh # 拒绝推送 committer date 早于指定日期或晚于今天的提交 z400000000000000000000000000000000000000000 while read local_ref local_sha remote_ref remote_sha do if [ $local_sha $z40 ]; then # 删除远端分支的推送跳过检查 continue fi if [ $remote_sha $z40 ]; then range$local_sha else range$remote_sha..$local_sha fi for commit in $(git rev-list $range); do cdate$(git log -1 --format%cI $commit) today$(date %Y-%m-%d) case $cdate in $today*) ;; *) echo 提交 $commit 的时间 $cdate 不是今天拒绝推送 exit 1 ;; esac done done exit 0这段脚本的核心思路是解析git push传入 stdin 的引用信息对待推送范围内的每一个提交检查%cI严格 ISO 8601 格式的 committer date如果不在今天直接退出并拒绝推送。注意pre-push钩子是本地行为只约束执行git push的人。远程仓库如果有服务端钩子或者平台级的代码规范检查效果会更好但那就是另一个话题了。4.2 一键提交固定时间git alias 的 commitat钩子负责拦截但我们自己也需要一个方便的工具去生成指定时间的提交。Git 别名可以封装一段 shell 函数把时间参数传给环境变量git config --global alias.commitat !f() { ts$1; shift; GIT_COMMITTER_DATE$ts GIT_AUTHOR_DATE$ts git commit $; }; f之后就能这样用git commitat 2024-01-15 10:30:00 0800 -m feat: add report module这里的时间参数是第一个位置参数剩下的参数全部原样传给git commit。所以-m 提交信息、--allow-empty、--amend之类的选项都能正常用。如果你经常需要“把某几个提交的时间统一到某个过去的时间点”这个别名在补写历史时会非常好用。4.3 把推送动作挪到指定时刻定时任务与 CI另一些同学搜“推送时间修改”想要的不是改历史提交而是让推送动作本身在某个时间点自动发生。这种情况核心是两个组合定时任务负责到点触发Git 命令负责推送。Linux 上可以用 crontab0 2 * * * cd /path/to/repo git push origin master这行配置表示每天凌晨 2 点自动执行一次git push。加上前面 4.2 的commitat你就能把“提交时间”和“推送动作”都固定在指定时刻。如果你用的是 GitHub Actions、GitLab CI 这类平台也可以直接配置 cron 触发器。流程一般是定时任务触发流水线流水线里先调整提交时间、再推送到目标分支。这样推送动作发生的时间和提交上记录的时间都能精确控制。这里有件事要想清楚推送动作本身的时间戳是记在远程仓库服务端的 reflog 或平台系统里的普通用户没有权限也改不了。你能改的只是 commit 对象里的作者时间和提交者时间远程页面显示“提交时间”时用的通常是这两个字段。所以“推送时间修改”这个说法严谨一点讲应该是“修改提交时间后重新推送”。5. 落地手册常见问题与避坑清单5.1 远程“更新时间”为什么改不动这是被问得最多的问题。本地把所有时间都改成过去的某一天强制推送到远端结果仓库页面右上角的“更新时间”还是现在。原因很简单那个“更新时间”指的是仓库整体在平台侧最近一次收到推送写入的时刻是服务端记录的只要 push 动作发生它就跟着刷新。它和 commit 里面的时间戳完全是两套体系。平台把这个信息用于仓库列表排序、动态展示普通用户无法通过修改 commit 字段去改变它。所以合理的预期应该是提交列表里的“提交时间”可以改仓库整体的“最近更新时间”改不了。如果你的目标是后者要么接受现实要么换一种展示方案比如在仓库说明里标注最后实际提测时间。5.2 force push 的团队协作姿势修改历史涉及到强制推送时我最常说的三个原则先备份。改之前切一个backup/分支名例如git branch backup/feature-login确认远端结果满意后再删。用--force-with-lease不要用--force。前者会校验远端引用如果别人在你操作期间推了新的提交命令会失败给你一个保留现场的机会。推送前在工作群同步。这个动作很重要因为凡是拉取过旧历史的协作者都会面临本地分支和远端历史不一致的问题。最稳妥的恢复方式是让他们重新拉取而不是在原地硬 rebase。在受保护分支上平台一般会直接拒绝 force push。如果某个分支配置了 PR 流程建议就不要再改它的历史了需要修正时间就通过新的提交来追加说明代价小得多。5.3 常用命令速查表场景命令查看提交的 author date 和 committer dategit log --formatfuller只改最近一条的作者时间git commit --amend --no-edit --date2024-01-15 10:30:00同时改最近一条的两个时间GIT_COMMITTER_DATE2024-01-15 10:30:00 0800 GIT_AUTHOR_DATE2024-01-15 10:30:00 0800 git commit --amend --no-edit在 interactive rebase 中改指定提交把pick改为edit停住后用环境变量执行git commit --amend --no-edit批量重写所有历史时间git filter-branch --env-filter export GIT_AUTHOR_DATE...; export GIT_COMMITTER_DATE... -- --all用 alias 生成指定时间的提交git commitat 2024-01-15 10:30:00 0800 -m message强制推送但不覆盖远端新提交git push --force-with-lease origin branch5.4 我踩过的几个坑最后聊几个真实的教训。第一个坑是时区。有次我在 UTC 环境上帮一个东八区的项目整理历史写日期时省了时区后缀。本地 log 一切正常推到 Gitee 上所有提交都比预期晚了 8 小时。后来排查才发现Git 解析没有时区的时间字符串用的是执行命令那台机器的时区。现在我的脚本模板里所有时间都带0800或者直接用开头的 Unix 时间戳彻底避免歧义。第二个坑发生在 rebase 之后。我以为只改了指定提交结果发现这个提交后面的所有提交的 committer date 全部变成了当前时间。后来理解了 rebase 的机制才明白重放提交就是重新创建提交对象不显式设置环境变量就不可能保留旧时间。所以只要是 rebase 操作想保住时间线就得在每个重放步骤里重新带上GIT_COMMITTER_DATE或者在脚本层面用rebase --exec批量执行环境变量设置。第三个坑是 filter-branch 的孤儿对象。改完大仓库后仓库体积不减反增就是因为没有清理refs/original/和 reflog。后面凡是做历史重写我都会把 3.3 节那三行清理命令放到收尾步骤里做成一个固定流程。这三个坑本质上都是同一个问题Git 的提交对象是不可变的任何“修改时间”的操作实际都是“生成一个新的提交对象并替换引用”。理解了这一点再看到 hash 变化、rebase 后时间被刷新、远程页面排序异常这些现象就不会觉得诡异了。
返回列表