zoukankan      html  css  js  c++  java
  • git自定义项目钩子和全局钩子

    钩子介绍

    自定义钩子分为:项目钩子和全局钩子

    自定义全局钩子:

    全局钩子目录结构:

    (注意:excludes目录结构是我们自定义的目录,规则逻辑在update.d/update.py脚本里实现的,非gitlab官方提供功能)

    /opt/gitlab/embedded/service/gitlab-shell/custom_hooks
                                                                                 ├── excludes
                                                                                 │            └── excludes.txt
                                                                                 └── update.d
                                                                                               └── update.py

    钩子目录:

    自定全局钩子目录: /opt/gitlab/embedded/service/gitlab-shell/custom_hooks  ,其中 update.d/update.py  是我们自定义的钩子脚本, 脚本使用python语言。

    如何扩展和修改全局钩子指定目录?

    1.修改 /etc/gitlab/gitlab.rb  配置文件中的配置项:gitlab_shell['custom_hooks_dir'] = "/opt/gitlab/embedded/service/gitlab-shell/custom_hooks"

    2. 执行 sudo gitlab-ctl reconfigure 命令来使配置生效。

         Tips:不要尝试直接修改 <gitlab-shell>/config.yml 的文件内容,文件中有说明: 
         This file is managed by gitlab-ctl. Manual changes will be erased! To change the contents below, edit /etc/gitlab/gitlab.rb and runsudo gitlab-ctl reconfigure.

    3.在自定的 custom_hooks_dir 目录(即我们的custom_hooks目录)下可创建三个文件夹对应三类 server hook name :

      • pre-receive.d
      • update.d
      • post-receive.d
    • 在每个文件夹下可创建任意文件,在对应的 hook 时期,gitlab 就会主动调用
    • 文件名以 ~ 结尾的文件会被忽略
    • 如果想看这部分的实现细节可以看 <gitlab-shell>/lib/gitlab_custom_hook.rb 文件

    如何对单个项目排除全局钩子?

    在 /opt/gitlab/embedded/service/gitlab-shell/custom_hooks/excludes/excludes.txt   文件中新加一行你要排除的git的地址即可:例如: https://git.test.abc/example.git 。 (该功能是自定义实现的,非官方提供)

    自定义项目钩子

    项目钩子的目录为固定目录:

    <project>.git/custom_hooks/   例如我们的项目自定义目录为: /var/opt/gitlab/git-data/repositories/frontend/testWeb.git/custom_hooks/update   ,update是我们自定义的钩子脚本。

    钩子的执行顺序

    钩子将按照以下的顺序执行

    1. <project>.git/hooks/ - symlink to gitlab-shell/hooks global dir
    2. <project>.git/hooks/<hook_name> - executed by git itself, this is gitlab-shell/hooks/<hook_name>
    3. <project>.git/custom_hooks/<hook_name> - per project hook (this is already existing behavior)
    4. <project>.git/custom_hooks/<hook_name>.d/* - per project hooks
    5. <project>.git/hooks/<hook_name>.d/* OR <custom_hooks_dir>/<hook_name.d>/* - global hooks: all executable files (minus editor backup files)

    钩子校验规则

    1、提交合并代码到test分支前,需要先合并代码到dev验证通过后方能push到test

    2、提交合并代码到master分支前,需要先合并代码到dev、test验证通过后方能push到master

    3、不同分支不能相互合并(如果需要合并,请合并后删除一个不需要的分支方能提交)

    4、合并代码到dev、test、master,merge过程出错了怎么办?

    可以直接在dev、test、master分支上修改代码,但是需要在备注comment里填写FIX_MERGE_ERROR 就可以直接提交(但请不要正常的代码也使用这个命令!!!!!否则后果很严重)

    git钩子原理

    在编写钩子的过程中发现钩子原理如下:

    1.gitlab默认安装完毕后在server端会有两个比较重要的目录。

    <project>.git 目录例如:/var/opt/gitlab/git-data/repositories/test/automation.git (我们称这个目录为git的project) 目录树结构如下:

    ├── branches
    ├── config
    ├── description
    ├── HEAD
    ├── hooks -> /opt/gitlab/embedded/service/gitlab-shell/hooks    
    ├── hooks.old.1500269284
    │   ├── applypatch-msg.sample
    │   ├── commit-msg.sample
    │   ├── post-update.sample
    │   ├── pre-applypatch.sample
    │   ├── pre-commit.sample
    │   ├── prepare-commit-msg.sample
    │   ├── pre-push.sample
    │   ├── pre-rebase.sample
    │   ├── pre-receive.sample
    │   └── update.sample
    ├── info
    │   └── exclude
    ├── objects
    │   ├── info
    │   └── pack
    └── refs
    ├── heads
    └── tags

    这个目录结构和git本地项目.git 的目录结构相似。目录中保存了git操作相关的版本信息。在git本地修改的元数据信息提交后都会保存在该目录中。

    /opt/gitlab/embedded/service/gitlab-shell/hooks

    改目录是git默认钩子的目录。在git的低版本( GitLab 8.15.以下)中扩展全局钩子可以该目录的hooks目录中直接修改或新加,但是可能影响到原有的钩子,因此不建议使用,推荐方式见本章1.3 如何修改全局钩子。

    2.git 钩子的脚本代码中:  0 正常退出,用户可以 push;非 0 异常退出,中断提交(pre-receive 和 update)

    3.通过本人研究发下,钩子(脚本中调用的shell的命令)其实是在server端的 <project>.git  git目录 执行的。举例:当执行automation项目的全局钩子时钩子内执行的shell命令会在/var/opt/gitlab/git-data/repositories/test/automation.git 目录中执行,可以通过在钩子中pwd来打印当前钩子的执行目录发现的。

    示例代码的目的:这个功能本身就是只有已经提交到dev或者test分支的过的才允许merge到master分支。如果本地分支没有提交道过dev或者test分支是不允许merge到master的。这样是为了保护master分支的功能都是经过测试通过的,从而避免误(将未测试通过的代码)提交到master。

    钩子的python代码:

      1 #!/usr/bin/env python
      2 # coding=utf-8
      3 '''
      4 该脚本在pre-receive或post-receive钩子中被调用,也可以直接将该文件作为git的钩子使用
      5 若钩子为shell脚本,则需要加入以下代码调用该脚本:
      6 while read line;do
      7         echo $line | python $PATH/pre-receive.py
      8 done
      9 当用户执行git push的时候会在远程版本库上触发此脚本
     10 该脚本的主要作用:获取用户提交至版本库的文件列表,提交者及时间信息
     11 '''
     12 
     13 import sys, subprocess
     14 import re
     15 import os
     16 
     17 __author__ = "zhanghuiwen"
     18 excludPath ="/opt/gitlab/embedded/service/gitlab-shell/custom_hooks/excludes/excludes.txt";
     19 baseGitUrl="http://172.26.0.80:8081"
     20 
     21 
     22 class Trigger(object):
     23 
     24 
     25     def __init__(self):
     26         '''
     27         初始化文件列表信息,提交者信息,提交时间,当前操作的分支
     28         '''
     29         self.pushAuthor = ""
     30         self.pushTime = ""
     31         self.fileList = []
     32         self.ref = ""
     33 
     34 
     35 
     36     def __getGitInfo(self):
     37         '''
     38         '''
     39         self.oldObject = sys.argv[2]
     40         self.newObject = sys.argv[3]
     41         self.ref = sys.argv[1]
     42 
     43     # 跳过排除的项目
     44     def _skipExcludeProjects_(self):
     45         '''
     46          跳过扫描的项目
     47         '''
     48         rev = subprocess.Popen("pwd", shell=True, stdout=subprocess.PIPE);
     49         gitServerRepoPath = rev.stdout.readline();  # 路径'/var/opt/gitlab/git-data/repositories/alpha/testhook.git'
     50         paths = gitServerRepoPath.split("repositories");
     51         projectPath = paths[1];  # /alpha/testhook.git
     52         rev.stdout.close();
     53 
     54         # 读取配置中的文件
     55         lines = open(excludPath, "r");
     56         for line in lines:
     57             realLine = line.strip("
    ");
     58             result = realLine.replace(baseGitUrl,"")
     59             if projectPath.strip(" ").strip("
    ") == result.strip(" ").strip("
    "):
     60                 lines.close()
     61                 print ("例外项目允许不经过dev和test直接提交")
     62                 exit(0)
     63             else:
     64                 pass
     65         lines.close()
     66         # 继续执行
     67 
     68     def __getPushInfo(self):
     69         '''
     70         git show命令获取push作者,时间,以及文件列表
     71         文件的路径为相对于版本库根目录的一个相对路径
     72         '''
     73         rev = subprocess.Popen('git rev-list ' + self.oldObject + '..' + self.newObject, shell=True,
     74                                stdout=subprocess.PIPE)
     75         pushList = rev.stdout.readlines()
     76         pushList = [x.strip() for x in pushList]
     77         # 循环获取每次提交的文件列表
     78         for pObject in pushList:
     79             p = subprocess.Popen('git show ' + pObject, shell=True, stdout=subprocess.PIPE)
     80             pipe = p.stdout.readlines()
     81             pipe = [x.strip() for x in pipe]
     82             self.pushAuthor = pipe[1].strip("Author:").strip()
     83             self.pushTime = pipe[2].strip("Date:").strip()
     84 
     85             self.fileList.extend(['/'.join(fileName.split("/")[1:]) for fileName in pipe if
     86                                   fileName.startswith("+++") and not fileName.endswith("null")])
     87 
     88         uBranch = self.ref.split('/')[len(self.ref.split('/')) - 1]
     89         print '提交分支:  %s' % uBranch
     90         print '提交变动from:%s to:%s' % (self.oldObject, self.newObject)
     91         print '提交的commit:%s' % pushList
     92         # if uBranch == 'dev':
     93         #    return
     94         # 循环获取每次提交的文件列表
     95         for pObject in pushList:
     96             # 判断是否是merge commit,如果是merge commit则忽略
     97             gitCatFileCmd = ('git cat-file -p %s') % (pObject)
     98             p = subprocess.Popen(gitCatFileCmd, shell=True, stdout=subprocess.PIPE)
     99             pipe = p.stdout.readlines()
    100             pipe = [x.strip() for x in pipe]
    101             i = 0
    102             for branch in pipe:
    103                 if branch.startswith('parent '):
    104                     i += 1
    105             if i >= 2:
    106                 continue
    107 
    108             # 如果提交的带上的msg是FIX_MERGE_ERROR则可以通行(避免合错分支引起的问题)
    109             msgLine = pipe[-1]
    110             print msgLine
    111             if msgLine == 'FIX_MERGE_ERROR':
    112                 continue
    113                 # if not re.match(r'^(w+)-(d+)', msgLine):
    114                 #       print '33[1;35m %s 提交的信息没有带上jira编号,请确认添加 33[0m' % pObject
    115                 #       exit(-1)
    116             listCmd = ('git branch --contains %s') % (pObject)
    117             p = subprocess.Popen(listCmd, shell=True, stdout=subprocess.PIPE)
    118             pipe = p.stdout.readlines()
    119             pipe = [x.strip() for x in pipe]
    120             print 'commit:%s->所属分支:%s' % (pObject, pipe)
    121             # 如果是master分支push提交,必须先提交dev、test
    122             if 'master' == uBranch:
    123                 if 'dev' not in pipe or 'test' not in pipe:
    124                     print '33[1;35m 合并到master的分支必须先在dev、test上经过验证合并才能提交,具体错误提交的hash:%s 33[0m' % pObject
    125                     exit(-1)
    126             elif 'test' == uBranch:
    127                 if 'dev' not in pipe:
    128                     print '33[1;35m 合并到test的分支必须先在dev上经过验证合并才能提交,具体错误提交的hash:%s 33[0m' % pObject
    129                     exit(-1)
    130             branchs = set()
    131             isMaster = True
    132             for branch in pipe:
    133                 branch = branch.replace('* ', '')
    134                 if 'master' == branch:
    135                     isMaster = False
    136                     break
    137                 if 'test' == branch or 'dev' == branch or 'dev-permission' == branch or 'test-permission' == branch:
    138                     continue
    139                     # elif uBranch != 'master' and uBranch != 'test' and uBranch != 'dev' and branch != uBranch:
    140                     # print '33[1;35m 提交失败!你合并提交的分支来自于多个分支,请确认,你的分支%s,其他分支%s 33[0m' % (uBranch, branch)
    141                     # exit(-1)
    142                 branchs.add(branch)
    143             if len(branchs) >= 2 and isMaster:
    144                 print '33[1;35m 提交失败!你合并提交的分支来自于多个分支,请确认%s 33[0m' % pipe
    145                 exit(-1)
    146 
    147     def getGitPushInfo(self):
    148         '''
    149         返回文件列表信息,提交者信息,提交时间
    150         '''
    151         self.__getGitInfo()
    152         self._skipExcludeProjects_()
    153         self.__getPushInfo()
    154         print '========================================='
    155         print "Time:", self.pushTime
    156         print "Author:", self.pushAuthor
    157         print "Ref:", self.ref
    158         print "Files:", self.fileList
    159         print '========================================='
    160 
    161 
    162 if __name__ == "__main__":
    163     t = Trigger()
    164     t.getGitPushInfo()
    View Code
  • 相关阅读:
    VM VirtualBox安装Centos6.5
    桥接
    程序员工作心法
    策略模式-鸭子怎么飞-实例
    策略模式-用什么方式去上班呢 实例
    观察者模式-订报纸,语音呼叫系统实例
    门面(Facade)模式--医院,保安系统实例
    Promise实例的resolve方法
    Promise实例的any方法
    Promise实例的race方法
  • 原文地址:https://www.cnblogs.com/zhangshiwen/p/7747864.html
Copyright © 2011-2022 走看看