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