我有一个 git 存储库,最新版本中有大约 3500 个提交和 30,000 个不同的文件。它代表了多人大约 3 年的工作成果,我们已获得将其全部开源的许可。我正在努力发布整个历史记录,而不仅仅是最新版本。为此,我感兴趣的是“回到过去”并在创建文件时在文件顶部插入许可证标头。我实际上已经做到了这一点,但完全用虚拟磁盘运行需要大约 3 天的时间,并且仍然需要一点手动干预。我知道它可以快得多,但我的 git-fu 还不能完全胜任这项任务。
问题是:我怎样才能更快地完成同样的事情?
我目前所做的(在脚本中自动执行,但请耐心等待......):
-
识别将新文件添加到存储库的所有提交(其中只有不到 500 个,fwiw):
git whatchanged --diff-filter=A --format=oneline
-
将环境变量 GIT_EDITOR 定义为我自己的脚本来替换pick
with edit
仅在文件的第一行出现一次(您很快就会明白原因)。这是操作的核心:
perl -pi -e 's/pick/edit/ if $. == 1' $1
-
对于输出的每个提交git whatchanged
上面,在添加文件的提交之前调用交互式变基:
git rebase -i decafbad001badc0da0000~1
我的自定义 GIT_EDITOR (perl 一行)发生了变化pick
to edit
我们将进入 shell 对新文件进行更改。另一个简单的header-inserter
脚本在我尝试插入的标头中查找已知的唯一模式(仅在已知的文件类型中(对我来说是*.[chS]))。如果不存在,则会将其插入,然后git add
是文件。这种天真的技术不知道在当前提交期间实际添加了哪些文件,但它最终做了正确的事情并且是幂等的(可以安全地针对同一文件多次运行),并且无论如何都不是整个过程的瓶颈。
此时,我们很高兴我们已经更新了当前提交,并调用:
git commit --amend
git rebase --continue
The rebase --continue
是昂贵的部分。由于我们调用了git rebase -i
输出中的每个修订一次whatchanged
,这是很多变基。该脚本运行的几乎所有时间都花在观察“Rebasing (2345/2733)”计数器增量上。
它也不仅仅是慢。必须定期解决一些冲突。至少在这些情况下(但可能更多)可能会发生这种情况:(1)当“新”文件实际上是现有文件的副本时,对其第一行进行了一些更改(例如,#include
声明)。这是一个真正的冲突,但在大多数情况下可以自动解决(是的,有一个处理该问题的脚本)。 (2) 当文件被删除时。只需确认我们想要删除它即可轻松解决git rm
。 (3)有些地方看起来像diff
只是表现得很糟糕,例如,更改只是添加了一个空行。其他更合理的冲突需要人工干预,但总的来说它们并不是最大的瓶颈。最大的瓶颈绝对是坐在那里盯着“Rebasing (xxxx/yyyy)”。
现在,各个变基是从较新的提交到较旧的提交启动的,即从输出的顶部开始git whatchanged
。这意味着第一次变基会影响昨天的提交,最终我们将变基 3 年前的提交。从“新”到“旧”似乎违反直觉,但到目前为止,我不相信这很重要,除非我们改变多个pick
to an edit
当调用变基时。我害怕这样做,因为冲突确实会到来,而且我不想因试图一次性重新建立一切而应对冲突的浪潮。也许有人知道避免这种情况的方法?我一直想不出一个。
我开始研究 git 对象的内部工作原理1!看起来确实应该有一种更有效的方法来遍历对象图并进行我想要进行的更改。
请注意,这个存储库来自 SVN 存储库,我们实际上没有使用标签或分支(我已经git filter-branch
编辑掉它们),所以我们确实有一个直线历史的便利。没有 git 分支或合并。
我确信我遗漏了一些关键信息,但这篇文章似乎已经太长了。我将尽力根据要求提供更多信息。最后我可能需要发布我的各种脚本,这是有可能的。我的目标是弄清楚如何在 git 存储库中重写历史记录;不要争论其他可行的许可和代码发布方法。
Thanks!
2012年6月17日更新:博客文章以及所有血淋淋的细节。