这篇讲怎么拦住别人直接把代码推到 main。看完你可以在 设置 → 分支保护 里打开 需要代码评审 和 对管理员同样生效,保持强制推送和删除关闭,再亲手推一次确认被拒绝,然后用拉取请求把改动合进去。
AngusGit 界面上把合并请求叫作 拉取请求,和 GitLab 的 Merge Request、GitHub 的 Pull Request 是同一件事。设置分支保护需要仓库的管理权限。
下文用线上示例仓库 xlliu/test-pr-example。克隆地址是 https://git.anguskit.com/xlliu/test-pr-example.git。已经合并的拉取请求 #1 feat: 增加 VIP 折扣与优惠券策略 把 feature/add-discount 合进了 main,改动落在 CouponPolicy.java、OrderController.java、OrderService.java、PriceCalculator.java,一共增加 34 行。你自己验证「直接推送会被拒绝」时,请新建分支,不要覆盖 feature/add-discount。
1. 加上保护之后,什么会被拦下
先弄清规则生效后的效果,后面每个开关在干什么就好理解了。下表按这里推荐的配置填写;表里的合并卡,指拉取请求对话页上那张显示能不能合并的卡片。
| 操作 | 保护之后的结果 | 终端或页面提示 |
|---|---|---|
在本地执行 git push origin main |
被拒绝 | Branch 'main' is protected: direct push is not allowed, please open a pull request |
git push --force origin main |
被拒绝 | Branch 'main' is protected: force push is not allowed |
git push origin --delete main |
被拒绝 | Branch 'main' is protected and cannot be deleted(网页删除同样被拒) |
在网页上直接编辑 main 里的文件 |
转为新分支 | 编辑 按钮变成 新建分支并发起 PR |
| 拉取请求还没有人批准 | 不能合并 | 合并卡显示 此拉取请求尚未准备好合并 |
| 拉取请求得到一位批准,且没有冲突 | 可以合并 | 合并卡显示 此拉取请求已准备好合并,可点 合并拉取请求 |
分支保护只管分支。推送标签不受这条规则影响。
2. 给 main 添加保护规则
打开分支保护
进入仓库,切到 设置,在左侧选 分支保护。标题是 分支保护规则,说明是「保护分支免受意外更改」。
选择分支
点 添加规则。在 分支名称 里选
main。打开需要的开关
按下表设置。没提到的开关保持默认的关闭状态。
保存
点 添加规则。回到列表后,这条规则带 受保护 徽章。切到仓库的 分支 页签,
main那一行也会标出 受保护。
| 开关 | 本文的设置 | 它在决定什么 |
|---|---|---|
| 需要代码评审 | 打开,必需的评审人数 填 1 |
合并前至少要有几个人批准。打开这一项之后,直接推送 main 才会被拒绝;关掉它,别人仍然可以绕过拉取请求直接推送 |
| 对管理员同样生效 | 打开 | 关闭时,仓库管理员不受这条规则限制:可以直接推送 main,也可以在没人批准时合并拉取请求。团队里只有一两个人时,最常见的「保护了却没拦住」就是这个原因 |
| 需要状态检查通过 | 先不打开 | 要求某些检查(例如流水线、安全扫描)报告成功才能合并。仓库里还没有任何会上报结果的检查时,打开它会让合并卡一直等下去 |
| 要求安全检查通过 | 先不打开 | 要求安全扫描通过才能合并。仓库还没接入安全扫描时同样会一直等 |
| 限制推送权限 | 先不打开 | 指定只有哪些用户和团队能推送这个分支。本文的目标靠「需要代码评审」已经达到 |
| 允许强制推送 | 保持关闭 | 打开后,有人可以用 --force 改写 main 的历史 |
| 允许删除 | 保持关闭 | 打开后,main 可以被删除 |
页面把 允许强制推送 和 允许删除 放在危险分组里,并提示它们会降低保护强度。这两项必须关闭。如果打开对话框时它们已经是打开的,先关掉再保存,否则 main 仍可以被强推或删除。
3. 验证:直接推送会被拒绝
规则保存之后,先亲手试一次,确认它真的生效了。在本地 main 上随便提交一处改动,再推送:
git switch main
echo "test" >> README.md
git commit -am "test: direct push"
git push origin main
终端里会出现这一行:
! [remote rejected] main -> main (Branch 'main' is protected: direct push is not allowed, please open a pull request)
看到它,说明保护已经生效。这次被拒绝的提交还留在你本地的 main 上,下一节会把它挪到新分支,走拉取请求合进去。
如果推送成功了,回到上一节的表格检查两件事:需要代码评审 是否打开;你是不是仓库管理员,而 对管理员同样生效 没有打开。
4. 改走拉取请求,把改动合进 main
把改动放到新分支
在本地基于当前状态建一个新分支
fix-readme并推上去(新分支没有保护,推送会成功),再把本地main退回远端状态,免得下次推送又被拒。注意reset --hard会丢掉本地main上没保存的改动,执行前先确认改动都已经在fix-readme里。发起拉取请求
在浏览器里切到仓库的 分支 页签,在
fix-readme那一行点 与默认分支对比,再点 创建拉取请求,仓库和分支会自动填好。也可以从侧栏 拉取请求 点 新建拉取请求,源分支/标签 选fix-readme,目标分支 选main。标题 写清这次改了什么,描述 写为什么改,点 创建拉取请求。请一位同事评审
合并卡此时显示 此拉取请求尚未准备好合并,原因是评审还没完成。请一位同事在侧栏 评审 里打开这条请求,看过之后提交 批准。具体怎么看差异、写意见,见文末参考里的代码评审一文。
合并
批准人数达到 1、并且没有冲突之后,合并卡变成 此拉取请求已准备好合并。作者或维护者点 合并拉取请求,选择合并方式,点 确认合并。提示 拉取请求已合并 之后,
main上就有了这次改动。
第一步将本地提交挪到新分支的命令如下:
git switch -c fix-readme
git push -u origin fix-readme
git switch main
git reset --hard origin/main
git switch fix-readme
示例仓库里这条已经走完的请求是 #1。对话页标题旁是 已合并,副标题写着要把 feature/add-discount 合到 main。文件变更是上面四个 Java 文件。你自己新开的请求在还没人批准时,合并卡会停在 此拉取请求尚未准备好合并;批准人数够了、也没有冲突之后,才会变成 此拉取请求已准备好合并,这时才能点 合并拉取请求。
合并完成后,本地同步一下,main 就和远端一致了:
git switch main
git pull
5. 合并卡说「需要最新分支」时
在 需要状态检查通过 打开之后,规则里会多出一项 需要最新分支。打开它,意思是:源分支必须包含 main 上最新的提交,才能合并。这样合进去的代码,是在 main 最新状态上验证过的。
拉取请求开着的这段时间里,别人又往 main 合了东西,合并卡就会要求你先更新分支。在本地把 main 的新提交接到你的分支上,再推送:
git switch fix-readme
git fetch origin
git rebase origin/main
git push --force-with-lease
fix-readme 没有保护,所以这里可以强推。--force-with-lease 比 --force 安全:如果远端的 fix-readme 在你不知道的时候被别人推过新提交,它会拒绝覆盖。不习惯变基的话,用 git merge origin/main 代替第三行,最后一行改成普通的 git push,效果也是让分支包含 main 的最新提交。
6. 规则不生效或卡住时
| 你看到的 | 原因 | 怎么处理 |
|---|---|---|
直接推送 main 成功了 |
需要代码评审 没打开,或者你是仓库管理员而 对管理员同样生效 没打开 | 回到 设置 → 分支保护,打开对应的开关 |
| 合并卡一直写着状态检查还没通过 | 打开了 需要状态检查通过,但仓库里没有会上报这个检查的流水线或扫描 | 先关掉这一项。等检查真正跑起来,再把它在拉取请求里出现的名字填进 必需的状态检查上下文 |
| 合并卡写着评审还没完成,可是同事已经批准了 | 批准之后又推了新提交,之前的批准标成了 需重新评审 | 请同事再看一次新的改动,重新提交 批准 |
| 合并卡写着必须先解决冲突 | main 上有人改了同一处代码 |
在拉取请求页点 在线解决冲突,或按第 5 节的命令在本地处理后再推送 |
Branch 'main' is protected: force push is not allowed |
你在 main 上用了 --force |
不要强推 main。需要改写的提交放到新分支,走拉取请求 |
| 网页上点不了 编辑,只有 新建分支并发起 PR | 当前分支受保护,这是预期效果 | 填一个分支名称,点 创建并编辑,保存后在横幅里 创建 Pull Request |
常见问题
一条规则能保护多个分支吗?
一条规则对应 分支名称 里选的一个分支。release 之类的分支也要保护时,再为它添加一条规则。
分支保护和「设置 → 拉取请求」里的「需要代码评审」有什么区别?
分支保护只对你选的那一个分支生效,并且会在推送时直接拒绝。设置 → 拉取请求 里的 需要代码评审 对仓库里所有分支的拉取请求生效,管的是合并前要几个人批准、请谁来批准,不拦推送。两边都设了人数时,以分支保护里的为准。只想管住 main 时,用分支保护就够了。
只有我一个人维护仓库,也要开吗?
建议开,至少保持 允许强制推送 和 允许删除 关闭,防止误操作改写历史。只有一个人时没法互相评审,需要代码评审 可以先不开,等有第二个人加入再打开。
能指定必须由某个人来审吗?
分支保护只规定人数,不规定是谁。要按目录指定审查人,需要用 CODEOWNERS 文件配合分支保护。
小结
保护 main 的核心是三个开关:需要代码评审 打开并填人数,对管理员同样生效 打开,强推和删除保持关闭。保存之后先亲手推一次,看到 direct push is not allowed 再继续。以后所有改动都走「新分支、拉取请求、评审批准、合并」这条路,main 上的每一次变化都有人看过、有记录可查。
结论: 在仓库 设置 → 分支保护 为
main打开 需要代码评审(设好人数)和 对管理员同样生效,并保持 允许强制推送 与 允许删除 关闭,即可拦截直接推送并强制走拉取请求合入。