这就是
git filter-branch
的功能。
使用
git filter-branch
时,您需要列出应使用的提交和分支名称。正如文档所说(有点晦涩):
命令只会重写命令行中提到的正向引用……
在您的情况下,这可能意味着您想要使用
--all
来覆盖所有分支,这巧合地(或者确切地说,不是巧合地)也告诉filter-branch脚本查看存储库中找到的所有内容(即所有提交以及所有标签/注释标签)。这是因为
--all
参数被传递给
git rev-list
,它会列出所有提交(和注释标签)。
filter-branch脚本通过迭代每个命名修订版本来工作。对于那些是提交的,它会应用所有指定的(非标记)过滤器。在这里最合适的过滤器是
--env-filter
。
(对于那些是标记的,如果有,则应用给定的标记名称过滤器。如果没有给出,则不对标记进行任何操作。因此,您可能需要
--tag-name-filter cat
,如示例中所述。有关详细信息,请参见文档。)
一旦脚本应用了您的过滤器,它就会创建一个新的提交,
1其中包含您所做的任何更改。通常将您的过滤器传递给shell的
eval
,以允许您设置环境变量。在这种情况下,关键的环境变量是控制提交时间戳的两个变量:
GIT_AUTHOR_DATE
和
GIT_COMMITTER_DATE
。
您的环境过滤器应该从提交中提取现有日期,其ID在
$GIT_COMMIT
中给出。如果这些日期在要修改的范围之外,则可以取消设置相应的环境变量或将其设置为原始提交的日期,以便在新提交中也使用现有的日期和时间戳。但是,如果它们在您的“更改范围”内,则需要设置(并再次
export
)变量为所需的新值。
您需要完善这个过程(可能需要很多,并且它没有经过很好的测试),但环境过滤器可能看起来像这样:
--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的安装位置。