YiluPHP
这家伙很懒,什么都没有留下...

经验 php+git实现代码的增量发布,一个PHP文件100行代码实现自动化发布

浏览数 135315 最后修改时间
先来认识一下git对比两个版本文件变化的命令
git diff branch1 branch2 --stat-width=500 --stat-name-width=500 --diff-filter=D
--stat    显示出所有有差异的文件
--stat-width=500   每个文件信息显示的最多字符数,超出的字符会显示省略号
--stat-name-width=500   每个文件名(含路径)显示的最多字符数,超出的字符会显示省略号
--name-only   参数可以只显示文件名(含路径)
--stat-count=10   最多输出多少个文件的信息,超过此数量的文件会显示统计值
--diff-filter=D   只选择那些添加 (A), 复制 (C), 删除 (D), 修改 (M), 重命名 (R)的文件, 它们的类型(如 普通文件, 符号链接, 子模块, …) 是否改变 (T), 是否未合并 (U), 是未知 (X), 或它们的对崩溃(B). 任何过滤字符的组合(包括none)均可使用。当组合中包括All或none,如果任一文件匹配了其他选项,就选择了所有路径。如果没有文件匹配其他选项,什么都不做。
而加上
git diff branch1 branch2 --name-only | xargs zip update.zip
就可以用于解决服务器端增量创建目录的问题。当然本次例子需要删掉已经删除的文件。

我以前使用shell脚本实现了从git库发布代码到各个环境(见以下文章)
现在想做增量更新文件的优化,增量更新需要检测出运行环境中的git版本和最新版本之间的文件变化,即文件的新增、修改和删除,然后只对这些文件进行发布。先来了解一下以前shell脚本的发布流程:
  1. 把代码提交到git库中;
  2. 在运行服务器中使用git命令更新代码到源码目录;
  3. 把源码目录里所有文件拷贝到运行目录,即覆盖文件,如果有文件删除还是需要手工去删除的。
为什么要区分源码目录和运行目录呢?源码目录是受git控制的,经常需要修改运行环境里的代码进行调试,如果运行目录受git控制,则调试时修改文件后需要及时的还原代码或者提交代码,否则下次无法正常更新代码。但经常会忘记做这个收尾工作,导致一些不必要的麻烦,而把代码拷贝一份到运行目录去运行就可以避免这个麻烦。
之前更新代码时是整个目录拷贝覆盖过去的,有两个缺点:
  1. 把没有更新的文件一起拷贝,执行效果低;
  2. 如果有文件删除,需要手工去运行目录里删除。
这里就是要做这两个缺点的优化处理,我原本想在原来的shell脚本里继续实现,但是我对shell的字符串操作不熟悉,需要上网找资料才能实现,我擅长PHP,为何不用PHP实现呢?于是我立即行动,花了几个小时就搞定了,源码见下面。
文件名:update_git_code.php
<?php

//为了统计总执行时间,先记录下开始时间
$start_time = microtime(true);
print_r("
");

//在这里可以定义多个项目的配置
$projects = [
    //项目名称
    'yiluphp_com' => [
        //项目的源码目录,即受git控制的目录
        'source_dir' => '/data/git_source/www.yiluphp_com/',
        //运行目录,即拷贝过去的目标目录
        'target_dir' => '/data/web/www.yiluphp.com/',
    ],
];

if (empty($argv[1])){
    exit("请指定需要更新的项目(空格+项目名)

");
}

if (empty($projects[$argv[1]])){
    exit('项目名不存在:'.$argv[1]."

");
}
$project = $projects[$argv[1]];

//记录下当前的版本号
$cmd = 'cd '.$project['source_dir'].' && git log -1 --pretty --oneline';
exec($cmd, $output, $return_var);
if ($return_var===0 && count($output)>0){
    $output = explode(' ', $output[0]);
    $last_commit_id = trim($output[0]);
}
else{
    exit('获取当前的版本号失败:'.json_encode($output)."

");
}

//拉取最新的代码
$cmd = 'cd '.$project['source_dir'].' && git pull';
exec($cmd, $output2, $return_var2);
if ($return_var2!==0 || empty($output2)){
    exit('拉取最新的代码失败:'.json_encode($output2)."

");
}

//获取最新的版本号
$cmd = 'cd '.$project['source_dir'].' && git log -1 --pretty --oneline';
exec($cmd, $output3, $return_var3);
if ($return_var3===0 && count($output3)>0){
    $output3 = explode(' ', $output3[0]);
    $new_commit_id = trim($output3[0]);
}
else{
    exit('获取最新的版本号失败:'.json_encode($output3)."

");
}

if ($last_commit_id === $new_commit_id){
    exit("已经是最新代码,无需更新

");
}

print_r("
");
//打包差异文件,老版本号在前,新版本号在后
//如果没有zip和unzip命令则需要安装
//apt-get安装方法:apt-get install zip
//yum安装方法:yum install -y unzip zip
$cmd = 'cd '.$project['source_dir'].' && git diff '.$last_commit_id.' '.$new_commit_id.' --stat-width=500 --stat-name-width=500 --name-only | xargs zip diff.tar.zip';
exec($cmd, $output4, $return_var4);
if (count($output4)>0){
    //找出删除的文件
    $delete_files = [];
    //需要移动的文件
    $has_copy_file = [];
    foreach ($output4 as $value){
        if (strpos($value, 'zip warning: name not matched:')>0){
            $value = explode('zip warning: name not matched:', $value);
            $delete_files[] = 'rm -f '.$project['target_dir'].trim($value[1]);
            print_r("删除文件:".trim($value[1])."
");
            continue;
        }
        else if (strpos($value, 'updating:')===0){
            $value = substr($value,9);
            $value = explode(' (', trim($value));
            $has_copy_file[] = trim($value[0]);
        }
        else if (strpos($value, 'adding:')>0){
            $value = explode('adding:', $value);
            $value = explode(' (', trim($value[1]));
            $has_copy_file[] = trim($value[0]);
        }
    }
}
else{
    exit('打包差异文件失败,如果没有zip和unzip命令则需要安装,yum安装方法:《yum install -y unzip zip》,apt-get安装方法:《apt-get install zip》:'.json_encode($output4)."

");
}

//把增量文件解压到目标目录
if ($has_copy_file) {
    //-o:不必先询问用户,unzip执行后覆盖原有的文件
    $cmd = 'unzip -o ' . $project['source_dir'] . 'diff.tar.zip -d ' . $project['target_dir'];
    exec($cmd, $output5, $return_var5);
    if ($return_var5 !== 0 || empty($output5)) {
        exit('解压差异文件失败,如果没有zip和unzip命令则需要安装,yum安装方法:《yum install -y unzip zip》,apt-get安装方法:《apt-get install zip》:' . json_encode($output5) . "

");
    }
}

//删除需要删除的文件
if (count($delete_files)>0){
    $cmd = implode(' ; ', $delete_files);
    exec($cmd, $output6, $return_var6);
    if ($return_var6!==0){
        exit('删除需要删除的文件失败:'.json_encode($output6)."

");
    }
}

//删除压缩包
if (file_exists($project['source_dir'].'diff.tar.zip')) {
    $cmd = 'rm -f ' . $project['source_dir'] . 'diff.tar.zip';
    exec($cmd, $output7, $return_var7);
    if ($return_var7 !== 0) {
        exit('删除差异文件的解压包失败:' . json_encode($output7) . "

");
    }
}

//在屏幕上打印出新增和更新的文件
if ($has_copy_file){
    foreach ($has_copy_file as $file){
        $cmd = 'cd '.$project['source_dir'].' && git diff '.$last_commit_id.' '.$new_commit_id.' '.$file;
        exec($cmd, $output8, $return_var8);
        if ($return_var8===0){
            if (isset($output8[1]) && strpos($output8[1],'new file mode')!==false){
                print_r("新增文件:".$file."
");
            }
            else {
                print_r("更新文件:".$file."
");
            }
        }
        unset($output8, $return_var8);
    }
}
exit('发布成功!耗时:'.(microtime(true)-$start_time)."秒

");

CLI执行命令:php  /你的目录路径/update_git_code.php yiluphp_com
如果你的环境中php没有设为环境变量,则写php的全路径。
后面的yiluphp_com即项目名称,需要在变量$projects中定义好的。
直接运行PHP代码命令太长了,书写起来麻烦,写一个shell脚本来执行吧,脚本的文件名取短一点,以后执行起来方便输入,这里就叫 update_yiluphp,把它放在 /usr/sbin/ 目录里可以全局调用,即在处在任何目录都可以直接输入脚本的文件名进行执行。
vi /usr/sbin/update_yiluphp
在文件里输入以下内容
#!/bin/bash
php /你的目录路径/update_git_code.php yiluphp_com
修改脚本的权限,让它可执行
chmod  +x  /usr/sbin/update_yiluphp
这样脚本就写好了,无论你处在哪个目录都可以直接输入 update_yiluphp 进行代码发布,你还可以添加多个项目,只需要两步就可以做到:
  1. 在PHP代码的 $projects 变量中增加项目的配置;
  2. 新增一个不一样项目名称的shell脚本。
脚本运行后会在屏幕上打印出有哪些文件变动:
[root@root ~]# update_yiluphp 

remote: Enumerating objects: 5, done.
remote: Counting objects: 100% (5/5), done.
remote: Compressing objects: 100% (1/1), done.
remote: Total 3 (delta 1), reused 3 (delta 1), pack-reused 0
Unpacking objects: 100% (3/3), done.
From github.com:yourname/yiluphp_com
   f6262bc..eeaba28  master     -> origin/master

删除文件:file1.php
更新文件:file2.php
新增文件:file3.txt
发布成功!耗时:5.0279848575592秒

[root@root ~]# 

同时你还可以把php代码放在网页中执行,这样不需要通过ssh远程登录服务器就可以执行发布操作,再给发布代码做一个用户角色的权限控制,有权限的人的才能发布代码,还可以做到每发布一次代码都写一次日志,这样就可以查询到什么时候什么人做了发布代码操作。

我来说说