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. path.resolve和path.resolve的用法

    前言:要搞清楚path.join()和path.resolve的具体作用,最好自己搞个文件,用node跑一遍去测试一下.只有自己亲自动手实践了,才知道具体是怎么回事,才能真正的理解 一.path.jo ...

  2. 2017GPLT

    PTA天梯赛2017GPLT 7-6 整除光棍 给定一个不以5结尾的奇数\(x\),求出数字\(n\)使得\(n*x=11...111\),输出数字n和1的位数 题解:模拟竖式除法 我们一开始发现n只 ...

  3. [vsCode]Visual Studio Code左侧导航栏图标丢失

    人在摸鱼,闲着没事看了看没用过的git窗口,然后右键把导航栏快捷图标关了 打开设置(Ctrl+,),搜索sidebar 编辑 settings.json { "window.zoomLeve ...

  4. GDAL 安装

    命令总和 add-apt-repository ppa:ubuntugis/ppa apt-get update apt-get install gdal-bin apt-get install li ...

  5. unity Android路径的相关部分代码

    using System; using System.Collections; using System.Collections.Generic; using System.IO; using Uni ...

  6. win10安装MAT并单独配置jdk11

    1.下载MAT 2.下载jdk11 3.配置jdk11 MAT解压后,在MemoryAnalyzer.ini配置文件开头添加配置指向jdk11,如下: -vm C:\Program Files\Jav ...

  7. 解决pycharm中cv2报错问题,anaconda安装opencv

    写在前面的话:cv2 报错是因为没安装opencv安装包所导致,并且在pycharm终端不可使用pip install cv2 进行安装! 如何解决cv2报错: 首先,值得注意的是在pycharm中, ...

  8. 综合java admin后台记录

    在新冠将来未来的气氛下,做一些年终封箱吧,这个事没做完,但暂时可能也没时间做,待来年了 https://hooray.gitee.io/fantastic-admin/ https://github. ...

  9. nginx auth_basic uwsgi 目录简易认证。

    mkdir /etc/nginx/conf.d/auth_pwd touch /etc/nginx/conf.d/auth_pwd/xx.pwd htpasswd -c -d /etc/nginx/c ...

  10. c语言学习---void 数据类型

    这样的语法是错误的: void a = 10;  void表示无类型, 这样定义一个变量a, 编译器是无法知道给a分配多大的内存空间的 #include<stdio.h> #include ...