背景
某某你提交的代码有问题,怎么把没有验证的dev代码合并到master上去了……
某某你提交的代码有问题,怎么把我的代码覆盖掉了……
谁呀,怎么都没有把代码合并到dev、test直接合并到了master分支……
某某你提交的代码有问题,谁提交的代码格式怎么那么乱,我格式化一下代码提交比对怎么代码全变动了,怎么提交之前没有格式化代码呀~!
在工作中也许你会经常遇到上面的场景,确实让人很头痛,即使流程规范代码提交规范,代码格式规范(参照《阿里巴巴技术开发手册》规范)已经制定好,但是在实施的过程中总会有很多人不能很好的按照规范来执行,要么就是对git不够了解的新手,要么就是忘记,总之各种问题。
首先来看看我们团队制定的代码开发流程:
新功能开发流程:开发者会从远程master分支checkout下新的开发分支name-feature-date(分支的命名规则开发者名称-功能名称-时间),然后在该分支上本地开发代码,开发完成后合并代码到dev分支,并到jenkins执行build部署操作提供开发联调环境,开发确认可以提测后将自己的分支代码再合并到test分支,并告知测试已经合并,测试会到Jenkins执行build部署操作并开始测试,测试通过后通知开发者可以将代码合并到master分支,合并master分支的权限只有几个技术负责人才能操作,这个时候会对代码做简单的评审然后合并master,发布线上。
上面的流程看起来代码经过层层验证应该不会有很多问题,但是实际上再正真执行起来你会发现问题、冲突比我们想象的更严重,比如:不小心把别人的分支合并到自己的分支,不小心把dev代码合并到自己分支,合并解决冲突时把别人的代码覆盖掉,代码没有经过在dev分支、test分支上校验过直接合并到master分支等等……
好,有了上面的具体业务场景,下面我们针对以上问题来真正的去了解git hooks
概述
钩子(hooks)是一些在$GIT-DIR/hooks目录的脚本, 在被特定的事件(certain points)触发后被调用。这让你在开发周期内可以定制Git内在行为并触发想要的操作。
Git hooks常用场景包括鼓励一个提交准则,根据仓库环境更改项目环境,实施持续集成工作流。但是,因为脚本可以无限制定制,因此实际上你可以使用Git hooks自动化和优化工作流的各个方面。
所有的Git hooks工具都是当仓库中特定事件发生时执行的脚本命令。因此安装和配置他们其实很容易。Hooks(钩子)要么放在本地要么放在远程仓库,而且只响应所在仓库的行为。配置可以用在本地和服务端hooks.
钩子安装位置
Hooks放在仓库的.git/hooks目录下。当你初始化仓库时,Git自动安装了示例脚本。如果你进入.git/hooks目录,你会看到如下文件:
applypatch-msg.sample
pre-push.sample
commit-msg.sample
pre-rebase.sample
post-update.sample
prepare-commit-msg.sample
pre-applypatch.sample
update.sample
pre-commit.sample
这些都是git仓库在默认初始化时提供的示例脚本,如果想使用模板示例,可以直接通过复制对应的想要触发阶段的脚本cp pre-push.sample pre-push.sh,并授权脚本可执行chmod +x pre-push.sh 即可
git脚本触发阶段分类
上面列出的示例脚本会再不同阶段触发,触发的地方可大致分为两类脚本
1 本地钩子脚本
applypatch-msg.sample
pre-push.sample
commit-msg.sample
pre-rebase.sample
post-update.sample
prepare-commit-msg.sample
pre-applypatch.sample
pre-commit.sample
2 服务端钩子脚本
pre-receive.sh
update.sh
post-receive.sh
各个脚本执行的阶段如下:
GitLab脚本以及用户自定义脚本
现在商业公司基本上都会使用GitLab作为自己的代码仓库管理,GitLab有自己的权限管理系统GitLab提供了一系列的辅助功能如:
protect branch 保护分支功能
权限体系 限制没有权限的人不能提交到master分支
当我们在GitLab上创建项目仓库时,对应GitLab服务器上的目录结构如下:
可以看到GitLab为我们创建了一个软连接连接到了GitLab自定义的钩子目录,这样所有创建的项目都可以使用同一个脚本规则,减少了维护成本。因为GitLab是作为仓库管理,所以脚本都是Remote脚本
GitLab会通过上面的三个脚本做相关的权限校验。
那我们如何结合GitLab定制自己的脚本呢?git会首先触发GitLab的脚本,然后GitLab执行完自己的脚本文件后会再调用掉用户放在custom_hooks下的脚本,所以我们只需要将我们定制好的脚本放在custom_hooks下即可,脚本名称和之前一样。
限制推送脚本定制
首先回到开篇的那些吐槽,怎么做好限制,让大家按规范执行。
规范1:代码提交流程必须进过dev、test、master,不能跳跃提交
要实现上面的功能我们就需要定制update.sh脚本,在开发者提交push推送代码到仓库时执行相关检查。在写钩子脚本时需要了解解决一下几个问题:
1、开发人员在执行git push时都提交了什么?
熟悉git的同学都应该知道,在我们执行git push本质就是将本地的所有commit信息提交到远程,而每个commit都有对应的一个hash值,通过这个hash值可以获取得到很多commit相关的信息,如当前commit提交人、提交时间、提交内容、提交的msg等,通过git cat-file -p commit-hash可以查看到具体的提交内容如下:
可以看到很多信息是我们可以使用的,如commit-msg,如果我们团队想要限制统一规范开发者提交的msg内容从而方便阅读,如有使用JIRA做缺陷管理系统的同学也可以定制提交的msg必须按照 “JIRA编号-msg内容” 提交,这样方便开发测试上线时拉取代码关联到对应的bug或者需求。
2、获取得到了push的commit怎么判断当前提交的commit所属的分支呢?
通过git branch -r --contains commit-hash值可以获取得到commit所属的分支,这样以来就能够解决规范一了,当开发者提交代码时可以获取提交到的分支是dev、test还是master,如果不是这些分支可以直接放行,如果test分支则判断当前commit是否属于dev分支,不属于直接返回拒绝提交,当用户push代码到master分支时,判断当前commit是否属于dev、test,如果不是直接拒绝提交。
规范二:不能合并dev、test分支代码代码以到自己的开发分支
为什么不能合并dev、test分支代码到自己的分支呢?因为dev、test上有很多不稳定的带测试的代码,而我们团队成员在开发时,要求周更新,这样就形成一个流程这周可以上的功能都有哪些哪些分支先走完了dev、test验证通过方能合并到master分支上发布上线。
那如何解决这个问题呢?
思路也是一致的就是遍历判断当前开发者这次push的commit是否有除了dev、test、master、自己的分支以外的任何分支代码,如果有则拒绝提交。
有了上面两个钩子确实解决了我们团队开发合并代码过程中的很多问题,代码合并出错的概率大大降低。但这个过程中也发现一些问题,如开发者在合并代码到master分支时发现冲突然后解决完冲突并推送代码到远程master分支,但发现解决冲突的方式有问题,这个时候需要重新修改master代码并推送到远程(因为dev、test分支是没问题的,只有master分支在合并有问题)但我们之前已经定义的钩子规则是任何一个commit提交都需要在dev、test才能合并推送到master,这种情况怎么解决呢?笔者是这样解决的这种合并出错的修复可以和开发者约定,如果需要直接推送到某个分支可以在提交的commit msg中以 FIX_MERGE_ERROR开始,这样钩子在检测commit时,会判断如果commit msg是以约定的头开始,则不走上面的规则校验,直接放行。
也许会有其他同学说着不就是后门吗?那么开发者岂不是都可以用这个后门破坏规则提交,恩,确实是这样,但是我们的出发点是开发者都是严谨的,我们的钩子是防止降低开发者无意识犯错的可能,也就是防君子不防小人,如果开发者都使用这种提交方式我们也有记录可追。
规范三:提交的代码需要符合公司的统一代码编码规范才能提交
我们的代码规范遵循目前业界比较流行的《阿里巴巴java开发规范手册》,具体怎么实现呢?这里我们结合sonarlint做代码风格检查并根据阿里巴巴开发手册定制了sonar插件,当开发者push代码时,会获取得到提交的文件,然后执行sonar插件校验代码风格,如果不符合代码规范则不允许提交。
目前sonar规范插件也在完善中。