基于git的自动集成php脚本
利用git进行版本管理我现在在做的东西,也不是一天两天了….
大概隔3-5天都会release一个版本放到正式线上去,但是同步代码的时候出现了一些困扰…..
正式线上的环境和本地的debug环境还有些不同,要发布的话需要编译一些文件,整理一些配置,添加ga代码等等操作,并不能直接patch到线上
之前一直都是靠人工的去看( 泥垢了! ),但是在项目代码规模日益庞大,开发进展迅速的现在有点撑不住了….= =
每天的提交修改量都在30Commits..(虽然有效的只有10+…..),但是去记住并验证修改了哪些文件已经不太方便了…
至少靠我一个人有点力不从心了…
最近一周花了点业余时间总算写出来了一个勉强能过去的ci脚本,放在这里供大家扔扔西瓜皮啊香蕉皮啥的…嗯(尼玛你这都算ci?别开玩笑了!噗~)
问题描述
首先得说明下,我确实没有玩过专业的ci工具,之前尝试gitlab-ci还安装失败了…..(太弱了!)
其实git本身提供了很好的diff -p的工具,能生成全世界通用的patch补丁,但是这也有一些问题.
- 首先是直接生成的补丁只能是一个或者多个,一个的话有些debug代码很难在线上直接patch,多个文件的话(一次release有时有上百个改动的文件)查找和管理又比较麻烦(貌似没有保证目录结构的patch?( 求指教! )
- 接着是线上线下环境有比较大的不同,对于我们的系统来说,需要做很多环境的改变,比如关闭调试模式,不显示运行时间,更新数据库链接地址和用户名密码等等….
- 就算有很好的逻辑结构化的patch文件也很难直接更新到线上..( 吧?)
基本上面的就是我现在的东西没法采用git自带diff -p的原因了.当然有可能只是针对我们现在的搓A项目来说确实不行…(如果我对项目或者git的理解有问题请务必交流!)
解决方案
既然git本身做不到,那么我们自己写个小小的脚本来解决问题就行了.(确实够 小 的..擦)
基于上面描述的问题,设计了下面这样的一套ci流程:
好吧.随便找了个在线画流程图的来了个惨不忍睹的示意图…
不过想要表达的都应该能说明了…..
说明
- 为了避免直接patch文件的不好操作性,采用了新的命令流程来一个个的处理文件
- 删除的文件不会进行实际操作,但是会在终端输出删除的文件名
- 新增和修改统一处理,在复制文件前调用hook,根据返回值来判断是否需要继续处理
- 处理win和lin下面对unicode的问题
- 所有文件处理结束之后的钩子
- 为什么要使用tag命令?至少从我了解的角度来说:
- tag是最不破坏当前库的workingtree的方式
- tag不是强制提交的话不会污染整个公有库
- tag可以随便打,打错了删掉也很方便( git真好 )
- 就算是提交的话也很有规律,在gitlab啥的查看也很方便
源码开始!
好吧.刚才说了那么多= =coder还是用自己的方式说话比较好~
需求:
- 软件 : 系统path里面能直接执行php与git.php拥有mkdir,chdir,copy函数调用权限
- 版本 : 不限.测试环境:
- php:5.4.9
- git:1.8.0
- 操作系统 : 任意(木有mac,所以没法测试,其他的系统测试没问题)
最近php写太多了…本来计划C++实现的…啥的…(你们什么都没看到!)
<?php
if( substr(php_sapi_name(), 0, 3) != 'cli' ){
die('You Should Use CGI/CLI To Run This Program.');
}
//win下存在unicode编码问题
$is_win = strpos(strtolower(PHP_OS),'win') !== false;
//start
//现在写死的参数,之后改成支持传参的方式
//git执行路径
$git_exec = 'git';
//版本库所在路径(相对路径)
//TODO 这里的处理,导致只能支持文件与项目目录平级,得想办法改成支持任何路径的方式
$project = 'abc-test';
//tag标签与消息的后缀
$version = date('Ymd');
//初始化需要的数据
//下面的tag名字和tag的msg让参数输入不太好,没法匹配了...写死的话也不太好,没法配置了...唉
$tag_msg = 'release of '.$version;
$tag_name = 'ci/release-'.$version;
//参见TODO
$current_path = getcwd();
$project_path = $current_path.'/'.$project;
//加载hook文件
//加载当前路径的hook.php文件,没有的话也无所谓
include('hook.php');
if(!function_exists('_before_copy_one')){
function _before_copy_one (){ return true; }
}
if(!function_exists('_after_all_copyed')){
function _after_all_copyed(){}
}
//切换php与git的工作目录
chdir ($project_path);
//检查当前的HEAD分支是否已有tag
exec ($git_exec.' log --oneline --decorate -n1 ',$out);
$has_tag = explode(' ',$out[0]);
if($has_tag[2] == 'tag:'){
//tag名字如果和预设的相同可以继续操作
$has_tag[3] = substr($has_tag[3],0,-1);
if($has_tag[3] != $tag_name){
die('You already have one tag on ref/HEAD named: '.$has_tag[3]);
}
}else{
//打上tag
exec($git_exec.' tag -m "'.$tag_msg.'" '.$tag_name);
}
unset($out);
//获取上次的relasetag标签
//下面的这句git语句是精髓吧...以打tag的时间的倒序的方式显示符合筛选条件的tag的前两个
//能够无视干扰的项目本身的tag,而且和tag名字后缀的啥的字符顺序无关
exec($git_exec.' for-each-ref --sort="-*authordate" --format="%(tag)" --count=2 refs/tags/ci/release-*',$out);
//这里检查了tag的情况,针对上一步中的tag情况再次验证
if($out[0] != $tag_name){
die('git tag ref/HEAD failed!');
}
$old_tag = $out[1];
unset($out);
//获取两次tag之间的全部文件变动
//diff的参数也是现学现卖的....最后的那个命令能巧妙的最简格式化输出,各位感兴趣的话可以在终端直接执行试试~
exec($git_exec.' --no-pager diff '.$old_tag.'..'.$tag_name.' --name-status',$out);
//判断只有一行或者输出失败的情况
if(count($out) < 2){
die('no file changed');
}
//开始复制
//下面的代码比较凌乱,没太多参考价值了....just works
$release_path = $current_path.'/'.$project.'_'.$version;
mkdir($release_path);
chdir ($release_path);
$files_count = count($out);
$files_deal = 0;
foreach($out as $one_line){
$action = substr($one_line,0,1);
$file = trim(substr($one_line,2));
if(!$file){
continue;
}
//该死的操作系统问题
if($is_win){
$file = iconv('UTF-8','gbk',$file);
}
$src_path = $project_path.'/'.$file;
$remote_path = $release_path.'/'.$file;
//先默认增加一次成功
$files_deal++;
//hook before real deal one file
if(!_before_copy_one($src_path,$action)){
continue;
}
//判断操作类型
switch($action){
case 'A':
case 'M':
//复制文件
$dir_path = dirname($remote_path);
if(!file_exists($dir_path)){
@mkdir($dir_path,0755,true);
}
$one_copy = copy($src_path,$remote_path);
if(!$one_copy){
//虽然有判断.但是实际使用的几次都没出现这个提示...
echo 'copy file failed: ',$file,chr(10);
$files_deal--;
}
break;
case 'D':
//删除文件的话给予提示就行
echo 'delete one file: ',$file,chr(10);
break;
}
}
//hook after all files have dealed
_after_all_copyed($files_deal,$files_count);
echo 'files dealed:',$files_deal,'/',$files_count,chr(10);
?>
两个hook只是为了对付我现在遇到的问题.没有什么通用性……
但是整个的流程就这么简单.增加hook啥的非常方便= =( 喂!,这破流程有和没有一样嘛! )
继续
如果看了源码之后对您有所启发的话那就再好不过了.
如果想更进一步的了解我是怎么使用这两个hook的话,下面可以接着着看一段使用的hook代码:
//file hook.php
<?php
function _before_copy_one($file_path,$type){
if(strtolower(end(explode('.',$file_path))) == 'less'){
global $has_less;
if($has_less){
//已经编译过了不再需要copy less文件
return false;
}else{
echo 'find less file add/modify, build css file.';
//编译less文件
//下面的代码就是调用实际编译的php程序了.关于这个在我之前的blog有详细的说明~
//也是和git集成了的哦~
chdir('lessphp');
//只能那个通过ob的方式才能完整的获取raw的gz数据
ob_start();
//gz之后的数据是bin的格式,直接用exec的话会出问题
passthru('php build.php');
$data=ob_get_contents();
ob_end_clean();
echo '.';
//由于输出带有header信息,去掉再gz解码
//这句是网上的神代码......超级好用
$data = gzinflate(substr($data,10,-8));
echo '.';
if($data){
//处理data.比如file_put_content到一个css文件啥的= =
}else{
echo 'failed: '.$data;
}
echo chr(10);
$has_less = true;
return false;
}
}
return true;
}
function _after_all_copyed($dealed,$all){
include('pclzip.lib.php');
$zip = new PclZip("{$release_path}.zip");
//就这么简单....
$v_list = $zip->create($project_path);
if($v_list == 0){
echo 'zip error'.$zip->errorInfo(true);
}else{
echo 'zip dir done!';
}
}
?>
说明:
这里需要注意的有两个地方吧:
- 通过一个global变量来判断一些已经处理过的参数,比如这里的
$has_less
,因为项目的设计,less只需要编译一遍,所以只要第一次遇到less有修改的话就可以保存这个状态,之后再调用的时候直接返回false,即不需要处理 这也是我设计单个文件的处理前hook的时候设计了返回值的意义 - 关于那个啥, php编译Less的程序 ,参见:( 全模式下的后台编译less
- 如果获取外部程序的二进制输出,这里给了个例子.推荐…
- 最后就是全部文件搞定之后的hook.直接打包成zip文件……一步到位
最后的自嘲
断断续续的花了5天时间来写这么一个破脚本代码…..最后的实用性还很低…唉.
不过还是那句话,重要的是启发大家思考解决问题.才能共同进步
如果有任何建议–意见–西瓜皮–口水啥的(喂) 热!烈!欢!迎!吐!槽!
原文地址: 基于git的自动集成php脚本 作者 : ZZJIN
转载请注明出处.