用推送规则和分支保护把质量门禁前移到代码落地之前
用推送规则和分支保护把质量门禁前移到代码落地之前
定义:推送规则(Push Rules)是极狐GitLab 提供的一组可通过界面配置的 pre-receive Git 钩子,用于在代码真正进入仓库之前校验提交者身份、提交消息、分支名称与文件内容。它与受保护分支、合并请求审批共同构成仓库侧的准入控制;其中推送规则为专业版与旗舰版功能,JihuLab.com 与私有化部署均可用。
大多数团队的门禁都建在流水线上:代码合进去了,跑一遍 SAST、跑一遍单测,红了再退回来修。这套流程没问题,但它有个天然的滞后——错误的代码已经进了仓库历史。清理历史比拦住一次 push 贵得多,尤其是当里面混进了密钥文件、超大二进制或者一条根本查不到需求来源的提交消息时。
把门禁往前挪一格,挪到 git push 那一刻,是成本更低的选择。本文讲的是极狐GitLab 里三件配合使用的东西:推送规则、受保护分支、合并请求审批,以及它们各自的边界和坑。
为什么门禁建在 push 之前更划算
流水线是事后校验,你付出的是一次完整的构建成本,换回一个"不许合"的结论。而 pre-receive 钩子是事前拦截,push 直接失败,代码一个字节都没进仓库。
两者的差异在三类问题上被放大:
密钥泄漏。一旦密钥进了历史,救火方式是轮换密钥 + 清理历史,前者要通知所有使用方,后者要 force push 并让全员重新拉取。
大文件。一个 200MB 的包被推上去之后,仓库体积永久膨胀,每个 clone 都要为此买单。
追溯断链。提交消息里没有需求编号,半年后回溯这次改动为什么做,只能靠翻聊天记录。
这三类问题的共同点是:事后修复的成本远高于事前拦截。这也是推送规则存在的理由。
推送规则能拦住什么
极狐GitLab 把常见的 pre-receive 校验做成了界面开关,不需要你写服务端钩子。校验维度分为五类。
校验提交者身份
拒绝未验证用户:提交者邮箱必须匹配用户已验证邮箱或私有提交邮箱之一。
拒绝不一致的用户名:作者与提交者邮箱一致时,作者名称必须与账号名匹配。
检查提交作者是否为极狐GitLab 用户:作者与提交者的邮箱都必须匹配到已验证的极狐GitLab 用户。
提交作者邮箱:用一个正则约束邮箱域,例如只允许公司域名。
注意一个例外:由极狐GitLab 自身签名的提交(例如通过 UI、仓库镜像产生的提交)使用实例邮箱作为提交者,前三项规则会跳过提交者邮箱检查,改为检查作者邮箱。
校验提交消息与分支名
这两类都用 RE2 语法的正则,每条正则上限 511 字符。
场景 | 正则示例 | 效果 |
|---|---|---|
提交必须关联需求 |
| 消息里必须出现类似 |
结尾不允许标点 |
| 消息最后一个字符是标点则拒绝 |
分支名前缀 |
| 分支必须以 |
分支名长度与字符集 |
| 只接受 4–15 位小写字母、数字、短横线 |
两个细节值得留意。第一,正则默认多行模式,可以用 (?-m) 关闭。第二,通过 Web UI 创建的提交消息换行符是 \r\n,写多行匹配时用 (\r\n?|\n) 而不是 \n,否则会误判。
另外,默认分支始终被允许;形如 40 位十六进制(类似提交哈希)的分支名出于安全原因默认禁止。
校验文件内容与标签
阻止推送密钥文件:基于一份预定义的文件名模式清单拦截,例如
.aws/credentials、id_rsa、id_ed25519等。要注意的是,它只拦新推送的文件,对仓库里已经存在的密钥不生效。禁止的文件名:自定义正则,例如禁止
*.pem、*.p12。最大文件大小:单位为 MB,设为 0 表示不限制;Git LFS 跟踪的文件不受此限制。
不允许用户使用 git push 删除 Git 标签:防止误删发布标签。
拒绝未签名提交:要求提交必须签名。
怎么配:三个层级与它们的继承语义
推送规则可以在实例级(管理员)、群组级、项目级配置。这里最容易踩的坑是它的继承语义——它是模板,不是继承。
具体规则是:
全局推送规则作为新项目的模板。配置之后创建的项目,会复制一份当时的规则。
项目推送规则是独立副本。项目创建后,全局或群组规则再改,项目不会跟着变。
从项目里删除推送规则,该项目将完全没有任何推送规则,它不会自动回落到群组或实例规则。
只有项目会继承推送规则,子群组不会从父群组继承。
第 3 条是真正的陷阱。很多团队发现某个项目规则不对,顺手删掉想"让它重新继承",结果发现项目变成了完全不设防的状态。正确做法是重新把项目的规则配成与全局一致,而不是删除。
配置路径:项目 → 设置 → 代码仓库 → 展开「推送规则」→ 保存。
与受保护分支、审批规则配合
推送规则管的是"什么内容能进来",受保护分支管的是"谁能往哪写",审批规则管的是"进来之前谁点头"。三者串起来才是完整的门禁。
受保护分支的关键设置是允许合并与允许推送和合并两个列表:
设置 | 控制范围 | 默认值 |
|---|---|---|
允许合并 | 谁能通过合并请求合并变更 | 无人可以合并(除非有推送和合并权限) |
允许推送和合并 | 谁能直接推送到该分支 | 无人可以推送 |
一个常被忽略的点:当「允许推送和合并」未配置时,它不会限制推送访问。想真正禁止直接 push,必须显式把它设为「无人」。
一个典型的最小权限组合是:
允许合并 = 维护者
允许推送和合并 = 无人
这样所有变更都必须走合并请求且由维护者批准。若希望开发者也能合并已批准的 MR,则把「允许合并」设为「开发者 + 维护者」,「允许推送和合并」仍设为「无人」。
分支名支持通配符(区分大小写),例如 production/*、*-stable,多条规则同时命中时取最宽松的那条。
审批规则方面,需要注意的是订阅级别的差异:基础版允许开发者及以上角色审批,但审批是可选的,不会阻止合并;专业版与旗舰版才能配置必需的审批数量与类型、按文件指定代码所有者(CODEOWNERS)、以及实例级与群级别的审批设置。配置路径是项目 → 设置 → 合并请求 → 合并请求审批,也可以通过合并请求审批 API 批量下发。
五个值得提前知道的坑
正则别写太长。单条上限 511 字符,超了会静默失败。复杂规则建议拆成多条或用「禁止的文件名」这类更简单的开关。
Fork 同步会绕过推送规则。从上游更新 Fork 时,变更直接应用,不走 Fork 的推送规则校验。不要把推送规则当作 Fork 场景下的安全边界。
删除规则等于解除武装。前面说过,项目级删除后不会回落到上层,这是最容易出事的误操作。
密钥文件规则不追溯历史。它只拦新推送的文件,历史里已有的密钥要靠 Secret Detection 扫描发现并轮换。
拒绝未签名提交会误伤。Web IDE 里创建的合法提交可能被拦,需要提前评估团队的实际提交方式。
什么情况下别这么做
团队还在早期、流程未定:先上严格的提交消息正则,只会让大家学会绕过它。先把规范写进文档并跑顺,再固化为机器校验。
分支名规则过于复杂:
^[a-z0-9\\-]{4,15}$这类强约束会让fix/temp-try这种临时分支也推不上去,反而催生本地堆积。把推送规则当安全合规的全部:它拦不住已经入库的内容,也拦不住 Fork 同步。合规口径上仍需 Secret Detection、SAST 与审计事件配合。
没有维护者带宽的小团队:把「允许推送和合并」设为无人、审批设为必需,会直接把维护者变成瓶颈。
FAQ
Q:推送规则和 CI 里的检查冲突吗?
不冲突,二者作用在不同的时间点。推送规则在 push 时执行,属于服务端 pre-receive 钩子;CI 检查在流水线运行时执行。建议把"格式类、成本极低"的校验放推送规则,"需要构建产物才能判断"的校验放 CI。
Q:能不能只给某些分支配推送规则?
不能。推送规则是仓库级配置,作用于所有 push,不区分分支。分支维度的差异控制要靠受保护分支实现。
Q:已经推上去的密钥文件怎么办?
推送规则无法处理历史数据。应当立即轮换密钥,并用 Secret Detection 做全量历史扫描,必要时清理 Git 历史。
想把这些规则一次性下发到上百个项目,靠界面点是不现实的。可以用合并请求审批 API 与项目级配置 API 做成脚本批量下发,并纳入仓库模板项目的初始化流程,让新项目一出生就带上门禁。
需要一份可直接执行的「仓库准入基线检查清单」?欢迎在评论区留言或联系极狐GitLab 团队获取,我们可以结合你当前的订阅等级与组织架构给出落地方案。

