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, 保证钩子执行的同时还能少按几次键。

使用git钩子防止合并分支的更多相关文章

  1. git创建与合并分支

    创建与合并分支 在版本回退里,你已经知道,每次提交,Git都把它们串成一条时间线,这条时间线就是一个分支.截止到目前,只有一条时间线,在Git里,这个分支叫主分 支,即master分支.HEAD严格来 ...

  2. Git(创建与合并分支)

    在版本回退里,你已经知道,每次提交,Git都把它们串成一条时间线,这条时间线就是一个分支.截止到目前,只有一条时间线,在Git里,这个分支叫主分支,即master分支.HEAD严格来说不是指向提交,而 ...

  3. 使用git创建与合并分支

    一.概述 学会使用git命令对项目进行创建分支,并在创建结束后合并到主分支上. 问:为什么要创建分支? 答:在原来的分支上创建一个自己的分支进行开发,在开发完毕后一次性合并到原先的分支,这样既保证安全 ...

  4. 【Git版本控制】Git的merge合并分支命令

    1.实例 git checkout master git merge dev merge合并分支只对当前分支master产生影响,被合并的分支dev不受影响. 假设你有两个分支,“stable” 和 ...

  5. Git创建与合并分支,撤销修改

    git回滚到指定版本并推送到远程分支(撤销已提交的修改,并已push) git reset --hard <commit ID号> git push -f git回滚到上一个版本并推送到远 ...

  6. 五、git创建及合并分支

    1. 创建并切换到dev分支 git checkout -b dev // git checkout命令加上-b参数表示创建并切换,相当于以下两条命令 git branch dev git check ...

  7. Git合并分支出现的冲突解决

    人生不如意之事十有八九,合并分支往往也不是一帆风顺的. 我们准备新的分支newbranch. LV@LV-PC MINGW32 /c/gitskill (master)$ git checkout - ...

  8. GIT 分支管理:创建与合并分支、解决合并冲突

    分支就是科幻电影里面的平行宇宙,当你正在电脑前努力学习Git的时候,另一个你正在另一个平行宇宙里努力学习SVN. 如果两个平行宇宙互不干扰,那对现在的你也没啥影响.不过,在某个时间点,两个平行宇宙合并 ...

  9. Git学习——创建与合并分支

    分支概念 当前我们所在的分支是master(主分支),可以通过创建分支: git branch <branch_name> 创建完成后,可以查看当前的分支状态: git branch 当前 ...

  10. Git 解决合并分支时的冲突

    参考链接:https://www.liaoxuefeng.com/wiki/896043488029600/900004111093344 创建分支时,新分支的文件内容建立在原分支的基础上,我们称这时 ...

随机推荐

  1. VS2019使用gtest

    VS2019使用gtest GoogleTest测试框架介绍(一)_liitdar的博客-CSDN博客_goole test 术语 test/test case/test suit Meaning G ...

  2. yolov5s yolov8n 在自己数据集上测试比较(640*640)

    yolov5s -> 0.5map:  96.5 -> ncnn:75ms yolov8n -> 0.5map:  94.1 -> ncnn:52ms

  3. 微信小程序之配置业务域名踩过的坑

    1.在配置业务域名弹窗中保存按钮一直加载状态,无法保存则刷新当前界面后重新扫码打开该弹窗.2.检验文件一定要放在目标服务器域名下前端文件夹中,否则(放在后台代码文件夹中)访问不到地址.

  4. SQLSERVER自动备份数据库

    1. 通过操作系统的定时任务执行 创建两个文件,auto.bat和auto.sql,使用bat调用sql文件中的代码段 auto.bat内容 sqlcmd -S localhost,2433 -U s ...

  5. 生成虚拟mac地址通过dhcp获取ip,耗尽dhco地址池

    平台:kali 软件:dhcpstarv 命令 dhcpstarv  -i 网卡名称 -e 本机IP 查看dhcp服务器已经分发的地址 cat /tmp/dhcp.leases

  6. MySQL升级5.7.29

    采用卸载后升级的方式 参考:https://blog.csdn.net/liu_dong_mei_mei/article/details/104010567 1.卸载原有的MySQL: 之前是wind ...

  7. 关于 echarts 使用 geo 制作地图 tooltip 不显示问题(转)

    原文地址 我之前遇到过这问题,单独设置 tooltip 没效果,geo 下面也有 tooltip 属性,但是也不管用,网上查了一下说 geo 不支持 tooltip 提示框显示,就自己根据 echar ...

  8. 自定义注解+反射提取对象到map中

    一.问题:有时候我们与第三方接口对接传参时,需要将对象里的字段和值以map形式传给别人,此时可以借助其他的工具类,但是我个人用起来不太灵活,还会把多余的字段传给别人,因此我们自己动手搞一套 二.思路: ...

  9. Vue的hash/history模式

    hash路由模式 URL 中的 hash 值只是客户端的一种状态,向服务端发送请求的时候,hash 部分不会被发送: hash 值得改变会在浏览器的历史记增加访问记录,所以可以通过浏览器的回退.前进控 ...

  10. SATA硬盘的数据和电源接口定义(转)

    现在 SATA设备越来越普及,包括STAT硬盘和光驱基本都已经是 SATA接口的了,以前的老式电源输出接口一般都是20针供主板加上4针的电源供硬盘也就是说以前的电脑电源给硬盘供电没有设计15针 SAT ...