重写git仓库以更改提交的日期范围

4
假设我有一个包含1000多个提交的Git仓库。提交日期从2013年8月到现在(2014年8月)。所有提交都是由一个用户(我)完成的。
现在,出于某种原因,我需要使仓库中的所有提交看起来都发生在2014年3月至今之间。
这可以通过更改现有仓库或创建新仓库并重新提交所有更改来实现。
如果只有少量提交,我会手动检出每个版本,并使用Git文档中描述的--date开关将状态提交到新仓库中。
然而,由于提交数量太多,这是不可能的。

为什么有人踩了这个问题?它是一个完全合法的问题,而且写得很好。 - Max Yankov
使用 git filter-repo 更新答案 https://dev59.com/s7noa4cB1Zd3GeqPbf5x#60873857 - Unapiedra
2个回答

2
这就是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_DATEGIT_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的安装位置。


谢谢@torek提供了详细的答案,正是我所需要的。 - scholar123

-3
打出BASH :) 请注意,以下内容是根据记忆编写的,并未经过测试,因此我会添加注释以帮助解释。
# Run git logs to get all the commit ids you want
git log --before={2014-03-01} --after={2013-01-01} --author="your name" > filename

# Run grep, so you can isolate the commit ids
grep "commit" filename > commit_ids

# Run a bash loop on those ids and change date
for i in $(cat commit_ids); do echo $i; git commit  --amend --date "`date`" $i; done;

1
git commit --amend 只能替换最新的提交记录。(具体来说,它不允许使用提交记录 ID 作为参数。) - torek

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