运用phpcbf快速修复git代码风格 作者: 灯小笼 时间: 2017-06-18 分类: 工具 由于历史原因,团队之前对于php代码标准没有深入的认识,因此以前对于代码风格这件事就放羊了。后来,随着团队规模的扩大,我们逐步意识到了代码风格不同意对于整体合作的不利影响,再加上我们对于代码加入了review机制,看到各种各样不同风格的代码,总是会对代码本身意图的理解造成一定程度的障碍。在这样的背景下,我们开始在代码加入了强制性的标准——PSR2。而对于命名空间,由于老代码根本就没有考虑这块,而且命名空间的影响也不大,因此我们放弃了这部分标准。 我们对于代码风格的实施,采用了一种灰度的方式实现。即对修改的代码,削减了部分代码标准,而对新加的代码,标准则相对严格一些。 最近由于业务需要,对一些线下项目有了较多的接触,发现其代码风格已经是颇难接受了。一些代码大小写不统一,格式不统一,最普遍的问题,则是\t的使用。因此萌发将这个项目的整体风格都修正一下的想法。 基本思路 ----------- 因为我们代码风格的控制是基于phpcs的,因此修复代码的任务就交给phpcbf了。最简单的办法,最简单的命令就是 ```bash phpcbf ./ --standard=PSR2 --extensions=php ``` 这样就能对整体代码进行整体修复了。 不过,问题也是显而易见的,经过这样一次修复,在这次commit之后的代码再要通过git blame查询某行代码的修改者的话,就不再那么简单了。这也是之前的项目做完这样的修复之后遇到的最显著问题之一。因此,最好能避免掉这个问题。 那么,比较彻底的方式,当然就是对所有历史版本的文件都用phpcbf处理一次,这样就能让git库的所有历史版本都保持基本的代码规范(phpcbf只能处理简单的修复工作,并不能修复所有的有问题的代码),而且原来所有的历史变动信息也还在。当然,也有副作用,各个commit的版本号都会出现变动。不过,这个副作用比起前面git blame的问题,可以忽略了。 v1.0.0 -------- 最简单最直接也是最容易想到的办法,就是用git filter-branch命令,然后再用--tree-filter选项对整个历史代码格式化一下。代码如下: ```bash git filter-branch --tree-filter "phpcbf ./ --standard=PSR2 --extensions=php" --prune-empty -f ``` 这个命令里边,`filter-branch`这个命令对于git而言,算是一个核武器了,因为它可以通过脚本的方式对大量提交进行修改。比如,全局地修改邮箱地址,或者把某一个文件从每一个提交中都删除掉。 `--tree-filter`选项会把所有历史提交版本依次提取到指定目录里(默认是.git-rewrite,也可以通过-d选项指定此目录),然后通过此选项的值对应的命令对这些提交进行处理。 `--prune-empty`选项的话,会将重写历史后一些没有任何修改内容的commit直接去掉。也就是说,原来有1000个commit以后,加上这个选项后,有可能变成<1000个提交。 `-f` 如果临时目录已经存在,git会拒绝执git filter-branch命令,加上此命令后,则会强制执行此命令。 好,开始执行了,这个项目约有1200个commit,所以整体跑下来还真的是需要蛮长时间的。刚开始,由于代码量有限,还蛮快的,几秒钟就处理完了一个commit,但是越到后面,每次commit处理的时间就越长。不过,这个还不是主要的问题,主要的问题在于这样执行过程中,遇到一些有报错的,phpcbf进程的退出码不为0,会导致git中断处理流程。所以,得包装一下这个命令,使其退出码一直为0。因此,出现了升级版v1.0.1。 v1.0.1 ------- 写一个脚本phpcbf.sh。 ```bash #! /bin/bash phpcbf ./ --standard=PSR2 --extensions=php exit 0 ``` 同时,调用命令变成了: ``` git filter-branch --tree-filter "sh phpcbf.sh" --prune-empty -f ``` 这样一来,能保障即使phpcbf执行过程中即使有报错,也不会影响程序继续执行。不过,没有等执行完毕,我就中止了这个命令。因为,要跑完1200多个commit,还真得花上好几个小时,太耗时间了。怎么办呢?在不改动代码前提的基础下,我的想法是把代码放到内存里去执行。 v1.0.2 ------- 怎么放内存呢?听起来好玄乎,但是在linux系统上,却是很简单的事情——把代码放到/dev/shm/这个目录就行了。用df命令就可以看到这个目录所在了: ```bash $: df -h Filesystem Size Used Avail Use% Mounted on /dev/mapper/vg_livecd-lv_root 50G 28G 21G 58% / tmpfs 1.9G 0 1.9G 0% /dev/shm /dev/sda1 485M 40M 420M 9% /boot /dev/mapper/vg_livecd-lv_home 439G 264G 153G 64% /home /dev/mapper/lvm--data-data 483G 83G 375G 19% /data ``` `/dev/shm/`是linux下一个目录,`/dev/shm`目录不在磁盘上,而是在内存里,因此使用其工作效率非常高,直接写进内存。对于磁盘io不高的虚拟机,把文件IO较多的东东丢到这个目录来处理,还是蛮适合的。事实证明,这个也确实比在硬盘的目录里边处理要快很多。 不过,鉴于处理这个问题的时候,已经很晚了,而且,这样处理起来效率还是很低,所以,我就直接让其在机器里边跑着,睡觉去了。 ```bash nohup git filter-branch --tree-filter "sh phpcbf.sh" --prune-empty -f & ``` nohup命令可以保证,即使ssh断掉,这个进程也会继续执行,&则是表示进程进入后台运行。 就算是用了内存,就算是跑了好几个小时,早上我起来的时候一看,程序也还在跑着呢,看起来,这样的机制还是得来个大的升级才行。 v2.0.0 ------ 进入到2.0版本,是因为,我把原来的单进程的方式,修改成了多进程的模式。毕竟,phpcbf进程对于每个文件而言,都是独立运行的,不会对彼此有相互依赖,因此,升级到多进程模式是没有任何问题的,只需要考虑好使用过少个进程,怎么给它们派活就行了。废话少说,直接上代码。 ```php #! /home/work/php/bin/php $dir) { $dir = trim($dir); if (!$dir || $dir == '.' || $dir == '..') { unset($dirs[$key]); } // phpcbf处理的时候,如果没有任何一个可用文件,会报错,因此提前检查 $cmd = sprintf("find %s -type f -name '*.php'|wc -l", $dir); ob_start(); $count = system($cmd); ob_get_clean(); if (!$count) { unset($dirs[$key]); } } $dirs = array_values($dirs); for ($i = 1, $l = count($dirs); $i < $l; ++$i) { $pid = pcntl_fork(); if (!$pid) { $dealDir = $dirs[$i]; print "In child $i\n"; $cmd = sprintf("phpcbf --standard=PSR2 --extensions=php %s", $dealDir); echo $cmd.PHP_EOL; system($cmd); exit($i); } } while (pcntl_waitpid(0, $status) != -1) { $status = pcntl_wexitstatus($status); echo "Child $status completed\n"; } ``` 这样一来,多进程模式开始处理了,效率一下子上来了,原来五六个小时处理的事情,现在也能缩短了一个小时以内了。 ### 处理剩余的单个文件 前面处理的是二级目录里边的文件,还剩下一些单独的一级目录的文件,因为只有一个文件,所以直接在每个commit里边对单个文件应用phpcbf处理就行了。不过,这样下来,效率也不高,往往也需要几分钟才能处理完1个文件。后来,又总结了一下,提取出一种新的处理模式。 考虑到同一个文件在整个git历史了,实际上改动的次数不多,而且,如果原始内容一样,那么每次应用phpcbf之后,结果应该是一样的。因此,考虑通过缓存phpcbf结果的方式来加快处理速度。直接看代码吧: ```php 标签: none