git是一款实用的版本管理工具,我们通过git init初始化一个git仓库,git会在当前目录为我们生成一个.git/目录,用来管理我们的版本文件信息。

在这个目录中有一个二级目录.git/hooks/,它里面存放了一些git执行的钩子脚本,在git运行的不同时期,执行不同的钩子。我们可以通过编写一些钩子脚本控制它的工作流程,比如在代码提交时进行邮件通知、代码格式检验等。

本文介绍的是一种通过编写钩子防止分支合并的案例,场景是在开发中有一些远超前于当前分支的分支(比如beta分支),如不慎将其合入开发分支(feature分支),然后还提交了,会给项目带来不必要的风险。

考虑日常工作情况,先使用git add, git commit提交代码,然后git merge合并分支。git merge进行的是一种三方合并,git分析了当前版本(以下简称F版本)、待合并版本(以下简称M版本)和它们的第一个共同祖先(也即分裂出它们两者的那个版本)这三者的差异,然后进行判断。如果M包含F的所有修改,则默认采用fast forward合并模式,直接把F所在分支的指针向前移动到M所在分支,合并结束,且不会执行任何钩子。

另一种情况是F和M形成了分裂(M不能包含F,因为修改了不同文件或者修改了相同的文件且造成冲突),在解决了所有可能发生的冲突后,git合并F和M的修改,创建一个全新版本(图2)。值得注意的是,如果在merge操作时带上-no-ff参数,则会强制按照这种方式合产生新版本。

第一种情况:

第二种情况:

在merge过程中会依次触发prepare-commit-msgcommit-msg钩子;如果有冲突,则在此之前,解决冲突并commit后触发pre-commit钩子,具体流程见下图。

git merge xxx三方合并是否快进合并结束是否冲突解决冲突后commit执行钩子pre-commit执行钩子prepare-commit-msg编辑提交合并信息执行钩子commit-msg合并结束yesnoyesno

下面来看钩子的编写,在.git/hooks文件夹下存放着一些git自带的钩子范例,且都以.sample为文件后缀。如果想让它们发挥作用,直接去掉这个后缀即可。钩子使用bash语法,在脚本中exit一个非零值即可中断git的行为。

比如说,把prepare-commit-msg.sample文件更名为prepare-commit-msg,并重写代码:

#!/bin/sh
echo "hook: prepare-commit-msg"
exit 1

那么通过正常git流程无法提交代码了,因为不管是commit操作还是merge操作(非快进),prepare-commit-msg钩子都要执行,而它exit 1表明是非正常终止,git就直接放弃了后续操作。

再来说下解决前文问题的思路:

我们在prepare-commit-msg钩子中实现控制,预设一个黑名单,对于所有git merge xxx操作,读取xxx对应的分支信息,与黑名单进行匹配,如果匹配命中,则终止操作。

需要思考的子问题:

  1. 钩子应该能自动检测当前操作是merge还是commit (它们都会触发commit-msg)
  2. 需要一个检测机制,精确匹配当前merge操作的分支是否在黑名单中

问题一是容易处理的,钩子执行时在全局已经给了我们几个参数,可以通过$x语法获取到,比如说对于prepare-commit-msg钩子:

#!/bin/sh
echo "hook: prepare-commit-msg"
# 钩子的路径
# .git/hooks/prepare-commit-msg
echo $0
# 提交信息文件路径(这是一个临时文件)
# merge操作 .git/MERGE_MSG ; commit操作 .git/COMMIT_EDITMSG
echo $1
# 操作类型
# merge操作 merge ; commit操作 message
echo $2
exit 1

$1参数是提交信息文件路径,merge操作的文件路径为".git/MERGE_MSG",我们自然可以判断如果这个文件存在,则是merge操作。当然也可以直接用$2操作类型判断,但commit-msg钩子中没有$2参数。

再来看问题二,怎样才能精确匹配分支?首先要找到待合并的分支是啥,在merge操作过程中,git会在.git目录生成MERGE_HEAD, MERGE_MODE, MERGE_MSG三个文件,分别存放的是待合并分支的sha-1值,合并模式和合并信息,读取MERGE_HEAD文件即可获取分支信息,nice。

接下来,要获取黑名单中分支的sha-1值,它们保存在.git/refs/heads这个目录里,我们遍历这个目录读取对应分支名的文件,即可拿到sha-1值

接下来就是匹配操作,就不赘述了。代码如下:

#!/bin/sh

BLACKLIST=("beta" "gamma")
forbid_list=() if [[ -e .git/MERGE_HEAD ]]; then
heads=`ls .git/refs/heads` for bl in "${BLACKLIST[@]}"; do
if [[ -n `echo ${heads} | grep -oP "(^| )${bl} "` ]]; then
forbid_list+=(`cat .git/refs/heads/${bl}`)
fi
done merge_head=`cat .git/MERGE_HEAD`
for br in "${forbid_list[@]}"; do
if [[ ${merge_head} == ${br} ]]; then
echo -e "\033[41;37m 合并了黑名单中的分支 \033[0m\n\r"
echo -e "\033[41;37m 请使用 git merge --abort 命令终止合并 \033[0m"
exit 1
fi
done
fi

最后一点也是最重要的,前面提到过fastward合并模式无法触发任何钩子,所以必须使用强制产生新版本的--no-ff模式,钩子才能发挥作用。推荐做法是使用git别名: git config --global alias.mg 'merge --no-ff', 以后合并操作使用git mg xxx, 保证钩子执行的同时还能少按几次键。

最新文章

  1. 使用bat脚本添加JAVA_HOME和修改PATH
  2. Java设计模式(七) 模板模式-使用钩子
  3. An exception occurred during a WebClient request
  4. C#之Windows消息处理
  5. ubuntu 编译安装 srilm
  6. jobs 命令
  7. POJ 2369 Permutations
  8. JavaScript 判断是否为undefined
  9. c语言, objective code(new 1)
  10. C# 通过 oledb 操作Excel
  11. 有关怎样入门ACM
  12. Android版本28使用http请求
  13. spring 普通类注入为null,通过自定义SpringUtils解决
  14. 51nod1986 Jason曾不想做的数论题
  15. retrofit动态代理
  16. 算法(第四版)C# 习题题解——1.1
  17. Python -bs4介绍
  18. svn目标计算机主动拒绝
  19. linux命令(35):diff命令
  20. Zabbix忘记登录密码重置

热门文章

  1. ES实战-trying to create too many buckets
  2. 前端代码的js里面的内容被打包后都会以某种规则全部存在dist文件夹的js文件夹的app.xxx.js里面
  3. IaaS--云硬盘(何恺铎《深入浅出云计算》笔记整理)
  4. verilog 和system verilog 文件操作
  5. linux内核中根据函数指针追踪调用函数名
  6. 已知内存BUF单元开始的区域中存放有一组无符号字节数据,要求将这些数据按从小到大的顺序排列,排序后的数据依然放在原来的存储区中。
  7. Codeforces Round #843 (Div. 2) C【思维】
  8. 微信小程序级联选择器省市区选择器部分安卓手机兼容的问题:无法只选省份,必须选择到市
  9. R6-1 输入年份和天数,输出对应的年、月、日
  10. JDBC之ResultSet和元数据