git filter-branch 就是这样做的。
git filter-branch 列出了应该使用的提交和分支名称。正如文档所说(有点神秘):
该命令只会重写命令行中提到的正 refs ...
在您的情况下,这可能意味着您希望 --all 覆盖所有分支,这巧合(或不巧合,真的)还告诉 filter-branch 脚本查看存储库中的所有内容(即,所有提交,以及所有标签/注释标签)。这是因为--all 参数被提供给git rev-list,它列出了所有提交(和带注释的标签)。
filter-branch 脚本通过迭代每个命名的修订来工作。对于那些提交,它应用所有指定的(非标记)过滤器。这里最适合使用的是--env-filter。
(对于那些是标签的,如果有的话,它会应用给定的标签名过滤器。如果没有给出,它不会对标签做任何事情。因此,您可能需要--tag-name-filter cat,如示例中所述。有关详细信息,请参阅文档。)
一旦脚本应用了您的过滤器,它就会进行新的提交,1 包含您所做的任何更改。您的过滤器通常2馈送到 shell 的 eval,它允许您设置环境变量。在这种情况下,关键的环境变量是控制提交时间戳的两个:GIT_AUTHOR_DATE 和 GIT_COMMITTER_DATE。
您的环境过滤器应首先从提交中提取现有日期,其 ID 在$GIT_COMMIT 中提供给您。如果这些日期超出要修改的范围,您可以取消设置相应的环境变量,或者将其设置为原始提交的日期,以便现有的日期和时间戳将用于新的提交也是如此。但是,如果它们在您的“更改范围”内,您需要将变量设置(并再次export)为所需的新值。
你会想要/需要改进它(可能很多,而且它还没有经过测试),但是 env 过滤器可能看起来像这样:
--env-filter 'at=$(git log --no-walk --pretty=format:%ai $GIT_COMMIT) \
ct=$(git log --no-walk --pretty=format:%ci $GIT_COMMIT); \
export GIT_AUTHOR_DATE=$($HOME/scripts/massage-time $at) \
GIT_COMMITTER_DATE=$($HOME/scripts/massage-time $ct)'
其中$HOME/scripts/massage-time 是您编写的脚本,用于获取时间戳(此处,通过 %ai 和 %ci 格式化;选择您自己喜欢的格式)并将其按摩到您的转换范围内。事实上,您的massage 脚本可以直接使用环境变量$GIT_COMMIT,并简单地生成export GIT_AUTHOR_DATE=... 命令作为输出(因为同样,您提供的过滤器的输出被馈送到eval)。 (不过,出于测试目的,最好将提交 ID 作为参数。然后您可以手动确保它对各种示例提交执行正确的操作,然后再将其用作环境过滤器。)
一旦filter-branch 脚本完成了所有这些新提交,它就会进行引用名称重写,以将每个引用名称指向与原始副本对应的任何新副本提交。例如,如果refs/heads/master 曾经指向提交badface,而badface 的副本是deadb17,则脚本使refs/heads/master 现在指向deadb17。这几乎是所有 git 命令的工作方式:它们只是添加新内容到存储库,同时将旧内容留在其中,并创建或移动引用标签以指向新内容。如果旧的东西最终变得不被引用,git gc 可以在那时将其删除。
1此时它实际上运行了您的提交过滤器,但提供了一个默认过滤器来进行新的提交。如果您提供自己的提交过滤器,则提交成为您的责任;这允许您省略一些提交。
2eval 规则适用于除提交过滤器之外的所有内容。您可以自己检查 filter-branch 脚本以查看:它位于 git-core 目录中,通常位于 /usr/local/libexec/git-core 或 /usr/libexec/git-core 中,具体取决于 git 安装。