写在前面
之前在公司和外包公司协作开发的时候,遇到了一个问题,是外包那边的代码合并和我们这边的代码合并出错了
具体效果就是“合并之后,他们写的部分代码就没了”
他们说修复,结果就是重新提交之后强制推送了一次。
都不知道是谁教的规范……
所以我想着是在服务端禁用一下这个不安全也不合规范的行为
踩坑日记
本来以为很简单的,结果
又是一个坑……
百度AI解决方案
百度搜到一个解决方案,还是百度ai
给出来的,建立hooks/pre-receive
钩子
脚本内容:(有坑)
#!/bin/bash
while read oldrev newrev refname
do
# 检查是否使用了--force选项
if [[ $refname == refs/heads/* ]] && git rev-parse --verify refs/remotes/origin/$(basename $refname) > /dev/null 2>&1; then
echo "不允许使用--force选项进行推送。"
exit 1
fi
done
exit 0
本来以为挺好用,但实际发现仍然还是可以强制推送,这个脚本屁用没有!
豆包AI解决方案 及 代码不生效的原因
然后去豆包找了解决方案
你的钩子脚本尝试通过检查
refname
变量来检测强制推送,但这种方式存在问题:
- 无法直接检测
-f
参数:Git 钩子无法直接获取用户执行的完整命令行参数,所以无法直接判断是否使用了-f
或--force
。- 错误的检测逻辑:当前脚本只是检查推送的是否是分支且远程分支是否存在,但这并不能区分普通推送和强制推送。
脚本内容:
#!/bin/bash
while read oldrev newrev refname
do
# 只处理分支
if [[ $refname == refs/heads/* ]]; then
# 获取分支名称
branch=$(basename $refname)
# 检查是否是删除分支操作(如果newrev是0值哈希)
if [[ $newrev == 00000000000000000000000000000000000000 ]]; then
echo "删除分支操作被禁止。"
exit 1
fi
# 检查是否是强制推送(历史记录是否丢失)
if ! git merge-base --is-ancestor $newrev $oldrev; then
echo "禁止强制推送!请使用普通推送或rebase后再推送。"
exit 1
fi
fi
done
exit 0
这个就能够很好的解决了。
下面我给出详细设置方法。
设置方法
1. 客户端模拟实现强制推送(仅为了测试,可跳过)
这里我在本地的一个项目里,重复修改文件,正常推送后
然后用commit --amend
提交,覆盖上一次的提交内容
用了git commit --amend
命令之后,会显示一个编辑器,可以修改你的提交信息(当然我这里就不修改了,直接保存退出)
我尝试一下进行普通推送,很明显,是推不上去的,因为本地的这个提交已经在远端了
修改后的提交相当于覆盖了远端历史,直接推送肯定是无法推送的
2. 服务端增加钩子,阻止强制推送
2.1 找到服务端仓库的钩子目录
首先,去到服务端的git
仓库位置(你要修改哪个仓库就去到哪个目录)
比如像我之前写过的这样,自建的仓库教程里,/home/git/resp/类似目录
目录结构是一致的才可以进行下一步噢。
然后打开你的hooks
目录,这里存放着很多钩子的示例文件
.sample
结尾的都是示例文件不带后缀的才能够正常生效,比如我这里红线画的这个
然后需要创建这样一个钩子文件,我是用了pre-receive
钩子,豆包建议,用update
钩子会更好,内容如下:
#!/bin/bash
while read oldrev newrev refname
do
# 只处理分支
if [[ $refname == refs/heads/* ]]; then
# 获取分支名称
branch=$(basename $refname)
# 检查是否是删除分支操作(如果newrev是0值哈希)
if [[ $newrev == 00000000000000000000000000000000000000 ]]; then
echo "删除分支操作被禁止。"
exit 1
fi
# 检查是否是强制推送(历史记录是否丢失)
if ! git merge-base --is-ancestor $newrev $oldrev; then
echo "禁止强制推送!请使用普通推送或rebase后再推送。"
exit 1
fi
fi
done
exit 0
新增并保存此文件
ps:我这里保存的时候出了点小事故,我忘了用
sudo
权限去修改了导致保存的时候才发现无法保存,还好有办法解决
2.2 修改权限(不修改无法阻止强推)
保存之后,需要修改权限信息,不然的话,依然是可以强行推送的,如下:
这里说的是未开启配置,但实际上是找不到,无法执行这个pre-receive
钩子
下面我们来修改权限
可以看到,用sudo
保存后的文件,是root
所有者的,需要修改所属者
用命令修改:sudo chown git.git pre-receive
即可
然后修改这里的权限信息
sudo chmod 755 pre-receive
这样是修改为所属者拥有全部权限,其他人有执行权限
2.3 测试能否强推
这里就配置完成了,我们来测试一下。
发现确实拦截住了,代码规范又进一步!
附录
原理:该钩子会在每次推送时检查是否有历史记录丢失(强制推送的特征)
其他补充说明
- update 钩子:这是服务器端钩子,在接收推送时执行,比 pre-receive 更适合逐分支检查
- 分支删除:脚本中同时禁止了分支删除操作,如果需要允许删除某些分支,可以添加例外逻辑
- git 配置方式:另一种更简单的方式是在服务器仓库中设置
receive.denyNonFastForwards = true
,但这种方式无法提供自定义错误信息