jon*_*0x5 6 git perl git-rebase
我有一个git存储库,在最新版本中有大约3500个提交和30,000个不同的文件.它代表了来自多个人的大约3年的工作,我们已经获得了使其全部开源的许可.我正在努力发布整个历史记录,而不仅仅是最新版本.为此,我感兴趣的是"回到过去"并在创建文件时在文件顶部插入许可证标题.我实际上有这个工作,但完全用ramdisk运行大约需要3天,但仍然需要一些手动干预.我知道它可以快得多,但我的git-fu不能完成任务.
问题是:如何更快地完成同样的事情?
我目前做什么(在脚本中自动化,但请耐心等待......):
确定将新文件添加到存储库的所有提交(其中只有500个,fwiw):
git whatchanged --diff-filter=A --format=oneline
Run Code Online (Sandbox Code Playgroud)定义环境变量GIT_EDITOR是我自己的脚本,替换pick
与edit
只在文件的第一行包含一个时间(你会发现为什么不久).这是该操作的核心:
perl -pi -e 's/pick/edit/ if $. == 1' $1
Run Code Online (Sandbox Code Playgroud)对于git whatchanged
上面输出中的每个提交,在添加文件的提交之前调用交互式rebase:
git rebase -i decafbad001badc0da0000~1
Run Code Online (Sandbox Code Playgroud)我的自定义GIT_EDITOR(perl one-liner)更改pick
为edit
,我们将被删除到shell以更改新文件.另一个简单的header-inserter
脚本在我试图插入的标题中查找已知的唯一模式(仅在已知的文件类型中(*.[chS]为我)).如果它不在那里,它会插入它,并且git add
是文件.这种天真的技术不知道在当前提交期间实际添加了哪些文件,但它最终做了正确的事情并且是幂等的(对同一文件多次运行是安全的),并且不是这整个过程瓶颈的地方无论如何.
在这一点上,我们很高兴我们已经更新了当前的提交,并调用:
git commit --amend
git rebase --continue
Run Code Online (Sandbox Code Playgroud)
这rebase --continue
是昂贵的部分.由于我们git rebase -i
在输出中为每个修订调用一次whatchanged
,这就是很多重新定位.这个脚本运行的几乎所有时间都花在观看"Rebasing(2345/2733)"计数器增量上.
它也不仅仅是缓慢的.必须解决定期发生的冲突.至少在这些情况下(但可能更多)会发生这种情况:(1)当"新"文件实际上是现有文件的副本时,对其第一行(例如,#include
语句)进行了一些更改.这是一个真正的冲突,但在大多数情况下可以自动解决(是的,有一个处理它的脚本).(2)删除文件时.只需确认我们要删除它就可以解决这个问题git rm
.(3)有些地方似乎diff
表现得很糟糕,例如,改变只是增加一个空行.其他更合理的冲突需要人工干预,但总的来说它们不是最大的瓶颈.最大的瓶颈绝对只是坐在那里盯着"Rebasing(xxxx/yyyy)".
现在,单个rebase是从较新的提交启动到较旧的提交,即从输出的顶部开始git whatchanged
.这意味着第一个rebase影响了昨天的提交,最终我们将从3年前重新定位提交.从正在进行的"新",以"老",似乎违反直觉的,但到目前为止,我不相信它的问题,除非我们改变不止一个pick
一个edit
调用重订时.我害怕这样做是因为冲突确实到来了,而且我不想处理冲突的浪潮,试图一次性改变一切.也许有人知道避免这种情况的方法吗?我无法想出一个.
我开始研究git对象1的内部工作原理!看起来似乎应该有一种更有效的方法来遍历对象图并只进行我想要进行的更改.
请注意,这个存储库来自一个SVN存储库,我们实际上没有使用标签或分支(我已经git filter-branch
把它们删除了),所以我们确实有直线历史的便利.没有git分支或合并.
我确定我已经遗漏了一些关键信息,但是这个帖子似乎已经过了很长时间.我会尽力按要求提供更多信息.最后,我可能需要发布我的各种脚本,这是一种可能性.我的目标是弄清楚如何在git存储库中重写历史; 不要讨论其他可行的许可和代码发布方法.
谢谢!
更新2012-06-17:博客文章包含所有血腥细节.
使用
git filter-branch -f --tree-filter '[[ -f README ]] && echo "---FOOTER---" >> README' HEAD
Run Code Online (Sandbox Code Playgroud)
本质上会在文件中添加一个页脚行README
,历史记录看起来就像自文件创建以来就一直存在,我不确定它对您来说是否足够有效,但这是正确的方法。
制作一个自定义脚本,您可能最终会得到一个良好的项目历史记录,做太多的“魔法”(rebase、perl、脚本编辑器等)可能最终会以意想不到的方式丢失或更改项目历史记录。
jon(OP)使用这个基本模式来实现显着简化和加速的目标。
git filter-branch -d /dev/shm/git --tree-filter \
'perl /path/to/find-add-license.pl' --prune-empty HEAD
Run Code Online (Sandbox Code Playgroud)
一些性能关键的观察结果。
使用-d <directory>
指向 ramdisk 目录的参数(如/dev/shm/foo
)将显着提高速度。
使用其内置语言功能从单个脚本进行所有更改,在使用小型实用程序(如find
)时完成的分叉将使过程减慢很多倍。避免这种情况:
git filter-branch -d /dev/shm/git --tree-filter \
'find . -name "*.[chS]" -exec perl /path/to/just-add-license.pl \{\} \;' \
--prune-empty HEAD
Run Code Online (Sandbox Code Playgroud)这是 OP 使用的 perl 脚本的清理版本:
#!/usr/bin/perl -w
use File::Slurp;
use File::Find;
my @dirs = qw(aDir anotherDir nested/DIR);
my $header = "Please put me at the top of each file.";
foreach my $dir(@dirs) {
if (-d $dir) {
find(\&Wanted, $dir);
}
}
sub Wanted {
/\.c$|\.h$|\.S$/ or return; # *.[chS]
my $file = $_;
my $contents = read_file($file);
$contents =~ s/\r\n?/\n/g; # convert DOS or old-Mac line endings to Unix
unless($contents =~ /Please put me at the top of each file\./) {
write_file( $file, {atomic => 1}, $header, $contents );
}
}
Run Code Online (Sandbox Code Playgroud)