当前位置:   article > 正文

git 服务端钩子做代码检查_gitlab pre-receive

gitlab pre-receive

需求分析

在代码修改后可以对代码进行检查,比如代码规范检查、代码构建、单元测试等。我们需要禁止成员推送不符合规范的代码到服务端。

Git 钩子能在特定的重要动作发生时触发自定义脚本。钩子分为客户端和服务器端两类。使用客服端钩子可以在commit时,对本地代码进行检查,可以参考:使用git钩子对提交代码进行检查。考虑到客服端钩子需要每个成员单独配置,或者说不是一种强制手段,无法避免某成员跳过钩子,强制push代码到远程的行为,我们将主要研究如何使用服务端钩子来拦截非法push的问题。

服务端钩子介绍

钩子都被存储在 Git 目录下的 hooks 子目录中。 也即绝大部分项目中的 .git/hooks 。

下图是使用gitolite搭建的git服务端,里面有一些示例钩子。

如需使钩子生效,需把文件.sample后缀去掉,如update.sample改成update,并确保文件有可执行权限。钩子脚本可以使用Shell、Python、Perl等脚本实现。这些脚本会在特定的动作发生时被调用,还会传递一些重要的参数,方便后续使用。

各个钩子的介绍

pre-receive

处理来自客户端的推送操作时,最先被调用的脚本是 pre-receive。 它从标准输入获取一系列被推送的引用。如果它以非零值退出,所有的推送内容都不会被接受。 你可以用这个钩子阻止对引用进行非快进(non-fast-forward)的更新,或者对该推送所修改的所有引用和文件进行访问控制。

update

update 脚本和 pre-receive 脚本十分类似,不同之处在于它会为每一个准备更新的分支各运行一次。 假如推送者同时向多个分支推送内容,pre-receive 只运行一次,相比之下 update 则会为每一个被推送的分支各运行一次。 它不会从标准输入读取内容,而是接受三个参数:引用的名字(分支),推送前的引用指向的内容的 SHA-1 值,以及用户准备推送的内容的 SHA-1 值。 如果 update 脚本以非零值退出,只有相应的那一个引用会被拒绝;其余的依然会被更新。

post-receive

post-receive 挂钩在整个过程完结以后运行,可以用来更新其他系统服务或者通知用户。 它接受与 pre-receive 相同的标准输入数据。 它的用途包括给某个邮件列表发信,通知持续集成(continous integration)的服务器, 或者更新问题追踪系统(ticket-tracking system) —— 甚至可以通过分析提交信息来决定某个问题(ticket)是否应该被开启,修改或者关闭。 该脚本无法终止推送进程,不过客户端在它结束运行之前将保持连接状态, 所以如果你想做其他操作需谨慎使用它,因为它将耗费你很长的一段时间。

实现需求

从钩子的介绍来看,我们需要拦截推送,可以在pre-receive和update中通过返回值来标示是否需要拦截。

服务端钩子会接受到三个参数:

  • 被推送的引用的名字

  • 推送前分支的修订版本(revision)

  • 用户准备推送的修订版本(revision)

我们使用pre-receive来实现,可以使用shell脚本获取到传递的进来的参数,可以参考gitlab pre-receive钩子

  1. # pre-receive
  2. #!/bin/bash
  3. while read oldVersion newVersion branch; do
  4. echo ${oldVersion}
  5. echo ${newVersion}
  6. echo ${branch}
  7. done

此时我们虽然可以拿到最新的commit id,也就是最新提交的ref,但是如何把代码取出来呢?

在服务端仓库中,不能直接拿到提交文件,而是以git object存储的。

可以使用git diff 拿到修改内容的差异文件。但是当我们需要对整个代码做检查时,需要的不仅是差异文件,可以在服务器上再clone出一套代码存放在build_code文件夹中,再把本次更新的差异应用到clone出来的代码上。

  1. git@xxx:~/build_code$ git clone ~/repositories/testing.git
  2. Cloning into 'testing'...
  3. done.
  4. git@xxx:~/build_code$ cd testing/
  5. git@xxx:~/build_code/testing$ git branch
  6. * master
  7. git@xxx:~/build_code/testing$

注意:clone代码后,需确认当前代码的分支,这里默认使用master,如需改成其它分支,需checkout到对应的分支。

由于git diff 生成的文件不能记录新增和删除的文件,这里我们可以用尝试使用git patch打包,然后在clone的代码端apply patch。

  1. # pre-receive
  2. #!/bin/bash
  3. while read oldVersion newVersion branch; do
  4. # 只对master分支做检查
  5. result=$(echo ${branch}| grep "master")
  6. if [ "$result" != "" ];then
  7. echo 开始检查代码
  8. # 生成patch
  9. git format-patch $oldVersion..$newVersion
  10. cd ~/build_code/testing/
  11. unset GIT_DIR
  12. unset GIT_QUARANTINE_PATH
  13. git reset HEAD --hard
  14. # 应用patch
  15. git apply ~/repositories/testing.git/*.patch
  16. # 清空patch
  17. rm -rf ~/repositories/testing.git/*.patch
  18. #开始验证代码
  19. bash check_code.sh # 自定义的检查脚本
  20. if [ $? -ne 0 ]; then
  21. echo 检查代码失败
  22. exit 1
  23. fi
  24. echo 检查代码成功
  25. fi
  26. done

到这里,我们就做到了对push到远程的代码进行检查检查的功能,若检查失败会exit 1,使成员在客服端上push时,操作失败。

后续问题

简单测试使用,并未发现异常,但是当客户端push的代码和当前ref差异较大时(如有分支分叉情况),apply patch会失败,导致检查的代码并不是最新代码。所以我们必须改变思路,放弃使用patch的方式。

修改思路

通过git diff 获取修改、删除、增加、重命名文件的名字,然后在clone到build_code中的代码中,重现对应的操作。

查看修改文件的记录git diff --name-status $oldVersion $newVersion,可参考Git pre-commit hook : changed/added files

修改后的代码:

  1. # pre-receive
  2. #!/bin/bash
  3. mkdir_and_cp_file(){
  4. result=$(echo $1 | grep "/")
  5. if [ "$result" != "" ];then
  6. mkdir -p $2/${1%/*}
  7. echo mkdir_and_cp_file:$1
  8. fi
  9. # tempDir=temp
  10. # mkdir -p $tempDir
  11. # git archive --format tar.gz --output "$tempDir/output.tar.gz" $newVersion $1
  12. # tar zxf $tempDir/output.tar.gz -C $tempDir
  13. # cp $tempDir/$1 $2/$1
  14. # rm -rf $tempDir
  15. git show $newVersion:$1 > $2/$1
  16. }
  17. while read oldVersion newVersion branch; do
  18. # 只对master分支做检查
  19. result=$(echo ${branch}| grep "master")
  20. if [ "$result" != "" ];then
  21. echo 开始检查代码
  22. desPath=~/build_code/testing/
  23. # echo -e "\ncp file"
  24. gitDiff=`git diff --name-status $oldVersion $newVersion | awk '$1 == "M" { print $2 }'`
  25. for var in ${gitDiff}; do
  26. mkdir_and_cp_file ${var} $desPath
  27. done
  28. # echo -e "\nmkdir and add cp file"
  29. gitDiff=`git diff --name-status $oldVersion $newVersion | awk '$1 == "A" { print $2 }'`
  30. for var in ${gitDiff}; do
  31. mkdir_and_cp_file ${var} $desPath
  32. done
  33. gitDiff=`git diff --name-status $oldVersion $newVersion | awk '$1 ~ "R" { print $3}'`
  34. for var in ${gitDiff}; do
  35. mkdir_and_cp_file ${var} $desPath
  36. done
  37. # echo -e "\ndelete file"
  38. gitDiff=`git diff --name-status $oldVersion $newVersion | awk '$1 == "D" { print $2 }'`
  39. for var in ${gitDiff}; do
  40. rm $desPath/${var}
  41. done
  42. gitDiff=`git diff --name-status $oldVersion $newVersion | awk '$1 ~ "R" { print $2}'`
  43. for var in ${gitDiff}; do
  44. rm $desPath/${var}
  45. done
  46. cd $desPath
  47. unset GIT_DIR
  48. unset GIT_QUARANTINE_PATH
  49. #开始验证代码
  50. bash check_code.sh # 自定义的检查脚本
  51. if [ $? -ne 0 ]; then
  52. echo 检查代码失败
  53. exit 1
  54. fi
  55. echo 检查代码成功
  56. fi
  57. done

另外需要注意,build_code中clone的代码,在pre-receive中检查后,需要在代码确认和入后(也就是post-receive后)将此份代码同步到最新状态,以便在下次使用git diff重现对应操作时,代码是干净且最新状态。

以上是使用服务端钩子检查代码的一种方式,有更好的方法,欢迎提出。

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/小小林熬夜学编程/article/detail/725376
推荐阅读
相关标签
  

闽ICP备14008679号