接手团队代码协作流程这半年,最让我上火的不是业务代码本身,而是每天没完没了地给人提 PR。Gitee 上仓库一多,光是把分支提上来、选好 base、指派评审人这种机械操作,就能耗掉一上午。后来我索性写了一套自动创建 PR 的小工具,顺手把评审模式也接了进来,今天就把这套方案从设计到落地的完整过程聊一遍,希望能给同样被 PR 流程折磨的人一点参考。
这篇内容不挑基础,只要你在用 Gitee 管代码、被重复的合并请求创建流程烦过,或者正好想给团队上代码评审制度但不知道怎么落地,都可以直接照着做。核心就一件事:让机器去干“创建 PR”这种重复劳动,把人解放出来做真正有价值的代码评审。
1. PR评审模式到底在解决什么问题
1.1 先搞清楚“评审模式”的本质
Gitee 的 PR(Pull Request)本质上是一种“请求合并”的机制。你把自己的分支提交上去,请求把代码合并到目标分支里。而评审模式,就是在这个请求合并的过程中,加入了“人审”的环节——代码不能直接合,必须被指派的评审人看过、点过同意,门禁才最终放开。
我在团队里经常打一个比方:没有评审模式的 PR,就像去餐厅点完菜直接进后厨自己炒;开了评审模式的 PR,是必须经过传菜员核验菜品、大厨试味之后才能端上桌。前者效率高,但出错没人把关;后者多了一道工序,却能把明显的问题在出锅前拦住。
具体到 Gitee 上,评审模式不只是一个按钮,而是一套组合玩法:创建 PR 时要能指定评审人,仓库要配置合并保护规则来强制“评审通过才能合并”,甚至可以在自动化脚本里把评审人字段一起传进去。这三件事咬合在一起,才算是真正把评审模式跑通。
1.2 手工创建PR的日常有多痛
在没有自动化之前,我们团队每周要发两次版本,涉及十几个服务仓库。每次发版前,我需要挨个仓库操作:切到 release 分支,确认代码 push 上去了,然后打开 Gitee 网页,新建 PR,选 base 分支、写标题、贴描述模板,再手动搜索评审人并指派。
这个过程听着不复杂,但架不住量大。十几个仓库,每个仓库操作三五分钟,一个上午就没了。更糟的是,重复操作特别容易出错:有一次我把一个仓库的 base 分支选错了,从 release 合并到了 develop,幸好评审人及时发现,不然一堆开发中的代码就被带上了生产分支。
还有一类痛点是“漏指派”。网页上手滑一下,PR 建完了,评审人没指派,就在列表里躺着。没人看,也没人合,直到发布窗口过了才发现代码根本没过评审。这种事情出现两三次之后,我就意识到必须用脚本把创建 PR 这个动作标准化、自动化,把人为疏忽的可能性降到最低。
1.3 自动创建PR如何与评审模式结合
自动创建 PR 听起来就是把“点击按钮建 PR”换成“调 API 建 PR”,但真正有价值的是把它和评审模式打通。
我的做法是:脚本扫描指定仓库的 release 分支,如果发现分支领先于 master,就自动创建一个 PR,标题和描述按照统一模板生成,同时把评审人字段一起传给 Gitee。评审人池是固定的几个技术负责人,脚本按仓库和分支名做分配,保证每个 PR 有两个人被指派。这样评审人每天打开 Gitee,只会看到一个清晰的任务清单:这是需要我看的 PR,这是需要我拍板的合并请求。
整套下来,人要做的事情只剩一件:打开 PR,看代码,点通过或驳回。创建 PR 的机械劳动全部交给脚本。
1.4 这件事的影响范围
很多人觉得“自动创建 PR”只是个效率工具,实际落地之后影响面比想象中大不少。
首先是流程规范性。脚本生成的 PR 标题、描述、标签都是标准化的,不会出现今天叫“fix bug”明天叫“发版”这种随意标题,后面翻历史记录、做版本追溯都方便。
其次是质检前移。因为评审模式被强制开启,所有代码在合并前必须经过至少一个评审人确认。我们自己统计过,上线前被评审拦下来的问题,修复成本大概是线上事故修复成本的三分之一都不到。哪怕只靠这个制度拦住一次重大事故,工具和流程的投入就全值回来了。
再就是审计和交接。PR 本身记录了谁在什么时候提了什么改动、谁评审的、谁合并的,这套审计链在团队扩招、人员流动频繁的时候特别管用。新人接手老项目,看 PR 历史比看文档更直观。
2. 整体设计与工具选型
2.1 一条完整的自动化链路
在动手写代码之前,我先把整条链路在脑子里过了一遍。典型的自动创建 PR 流程可以拆成这么几步:
- 确定仓库列表和需要检查的分支组合。
- 获取每个仓库的分支信息,比对 head 分支和 base 分支是否存在差异。
- 如果存在差异,再查询是否已经存在相同的 PR,避免重复提交。
- 调用 Gitee OpenAPI 创建 PR,带上标题、描述、评审人和指派人。
- 反馈创建结果,输出 PR 地址,方便后续追踪。
这个链路里,最容易被忽略的是第 3 步。很多刚开始写自动化脚本的人,直接跳到第 4 步调接口创建 PR,结果同一对分支连续创建了好几个重复的 PR,把评审人烦得不行。我后来把查询已有 PR 的逻辑加在最前面,重复创建这个问题就彻底消失了。
2.2 备选方案横向对比
实现自动创建 PR 的方式其实有好几种,关键看团队的技术栈和维护成本。我先后试过几种,各有优劣。
| 方案 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|
| 原生脚本(curl / Python requests) | 灵活、可控、无额外依赖 | 需要自己维护 API 调用逻辑 | 团队有 Python 或 Shell 基础,追求轻量 |
| Gitee 官方 SDK | 封装了 API,调用更省事 | 更新维护节奏不一,文档偶尔滞后 | 想快速接入且熟悉语言生态 |
| Gitee Go 流水线插件 | 和仓库集成度高,无需自建调度 | 依赖 Gitee 平台,跨平台能力弱 | 本身就在 Gitee Go 上做 CI/CD |
| 自动化平台 + OpenAPI | 可视化编排,非开发也能用 | 引入额外系统,学习成本高 | 大团队统一管控运维平台 |
我自己最终选的是 Python 脚本方案。原因很简单:团队里 Python 熟练度最高,而且脚本可以直接挂在 CI 或定时任务里,不依赖任何额外的系统。Gitee 的 API 文档写得很清楚,直接用 requests 调用并不复杂。
2.3 令牌与权限的最小化设计
用 OpenAPI 操作 Gitee 需要认证,最常见的是使用私人令牌(Personal Access Token)。这块有个安全教训我必须强调:令牌一定要按最小权限原则申请,而且绝对不能硬编码在代码里。
我见过有人图省事,把令牌直接写进脚本文件,然后整个仓库提交到 Gitee 上。令牌一旦泄露,别人就可以用你的身份建 PR、改仓库设置、甚至删分支。正确做法是把令牌放到环境变量或 CI 的 Secrets 里,脚本运行的时候从环境里读取。
在 Gitee 上创建私人令牌的时候,权限范围可以精细勾选。对自动建 PR 这个需求来说,只需要 projects 相关的读取权限和 pull_requests 的写权限就够了,其他权限一律不勾。这样即使令牌真的不幸泄露,攻击者能做的事也被限制在一个很小的范围内。
3. 手把手写一个自动创建PR的脚本
3.1 准备环境与令牌申请
开始写脚本之前,先把环境准备好。你需要一个能跑 Python 的机器,Python 3.6 以上版本就行,再用 pip 装一下 requests 库:
pip install requests然后去 Gitee 申请私人令牌。路径是:Gitee 右上角头像 → 设置 → 安全设置 → 私人令牌 → 生成新令牌。生成时有两个点要注意:
- 权限范围勾选 projects(读取)和 pull_requests(写入)即可。
- 令牌只显示一次,生成后立刻保存到环境变量里。
Linux 或 macOS 下可以用 export 临时导出,CI 平台就放到对应的 Secrets/变量配置里:
export GITEE_TOKEN="你的令牌"脚本里读取环境变量的方式尽量统一,后面维护起来省心。
3.2 核心实现:自动建PR并指派评审人
下面这段是我在实际项目里跑过的简化版本。核心逻辑就两块:先查重,再创建。
import os import requests GITEE_API = "https://gitee.com/api/v5" TOKEN = os.environ.get("GITEE_TOKEN", "") OWNER = "your_org" # 组织或个人命名空间 REPO = "demo-service" # 仓库名 headers = {"Content-Type": "application/json;charset=UTF-8"} def list_pulls(state="open"): """列出仓库的所有PR,用于查重""" url = f"{GITEE_API}/repos/{OWNER}/{REPO}/pulls" params = { "access_token": TOKEN, "state": state, "per_page": 100, "page": 1, } pulls = [] while True: resp = requests.get(url, params=params, headers=headers) resp.raise_for_status() batch = resp.json() if not batch: break pulls.extend(batch) params["page"] += 1 return pulls def create_pr(title, head, base, body="", reviewers=None): """创建PR,并指定评审人""" url = f"{GITEE_API}/repos/{OWNER}/{REPO}/pulls" data = { "access_token": TOKEN, "title": title, "head": head, "base": base, "body": body, "reviewers": ",".join(reviewers or []), "assignees": ",".join(reviewers or []), } resp = requests.post(url, json=data, headers=headers) resp.raise_for_status() return resp.json() def find_existing_pull(pulls, head, base): """在已存在的PR里找相同分支组合""" for pr in pulls: if pr["head"]["ref"] == head and pr["base"]["ref"] == base: return pr return None if __name__ == "__main__": HEAD = "release/1.2.0" BASE = "master" existing_pulls = list_pulls("open") duplicate = find_existing_pull(existing_pulls, HEAD, BASE) if duplicate: print(f"已存在相同PR,跳过创建: {duplicate['html_url']}") else: pr = create_pr( title="release/1.2.0 -> master 发版合并", head=HEAD, base=BASE, body="自动化生成的发版PR,请重点检查配置变更和数据库脚本。", reviewers=["alice", "bob"], ) print(f"PR创建成功: {pr['html_url']}")两个细节值得展开说一下。第一,list_pulls 里我用了分页循环,per_page 设成 100,然后一页一页翻。一开始我只请求第一页,结果 PR 一多,查重逻辑就漏了,同一个分支组合被建了好几个 PR。加成分页之后才彻底修好。第二,reviewers 和 assignees 都传了同一个列表。区别在于 reviewers 是真正要做代码评审的人,assignees 是负责跟进这个 PR 的人。对大多数团队来说,这两个角色往往是同一个人,但也有分开的场景,比如 assignees 是实习生,reviewers 是导师。
3.3 评审模式的最终落地:合并保护
到这里,PR 能自动建了,评审人也指派了,但离“评审模式”真正生效还差最后一步——仓库的合并保护规则。
我自己一开始就吃过这个亏。脚本建完 PR、指派了评审人,我以为万事大吉了,结果同事直接在 Gitee 网页上点了“合并”按钮,PR 就合了,评审人根本没来得及看。原因很简单:光指派评审人只是一个“请求”,不等于“强制”。
真正的强制要在仓库设置里开。路径是:仓库 → 管理 → 分支设置 → 保护分支。对 master(或其他核心分支)开启保护,然后勾选“需要评审通过后才能合并”之类的选项。不同版本 Gitee 的菜单位置可能有差异,但核心逻辑是一致的:把“评审通过”设置为合并的前置条件。
这一步如果不想手工去点,也可以查一下 Gitee OpenAPI 有没有对应的接口,或者至少把配置方法写进团队文档里。我的建议是:不管用哪种方式,这个规则必须有人负责维护,否则评审模式就是纸糊的,看起来存在,一推就倒。
3.4 挂到定时任务或CI里
脚本写完之后,接下来就是让它按时跑起来。最简单的办法是 crontab。比如每个工作日上午十点检查一次所有仓库的 release 分支:
0 10 * * 1-5 cd /opt/gitee-pr-bot && python create_release_pr.py >> logs/pr-bot.log 2>&1如果你团队已经上了 Gitee Go 或者别的 CI 系统,也可以把脚本作为流水线的一个步骤。相比 crontab,CI 的好处是有现成的调度界面和日志中心,失败了能直接看到告警通知。
我在实际落地时选择的是“定时执行 + 人工兜底”的组合:脚本定时跑,跑完把结果发到团队群里;如果当天有临时需求要立刻提 PR,再手动补一个。机器负责例行公事,人负责异常情况,两边不耽误。
注意:脚本刚写完的时候,不要直接全量铺开跑所有仓库。先挑一个不重要的仓库试运行几天,确认查重逻辑、评审人分配、通知触达都正常了,再扩大范围。
4. 常见问题与排查记录
4.1 鉴权失败:401和403的区分
脚本上线之后最先遇到的就是鉴权问题,具体表现是调用 API 时报 401 或 403。
两者的含义不一样。401 是 Unauthorized,说明令牌本身有问题,可能过期了、被吊销了,或者压根没传进去。排查思路就三步:先确认环境变量 GITEE_TOKEN 有没有正常导出;再检查令牌是不是被误删;最后确认令牌内容有没有复制完整,比如多复制了一个空格,就会导致鉴权失败。
403 是 Forbidden,多半是权限不足。这时候重点检查私人令牌的权限范围,是不是只勾了 projects 读取而没勾 pull_requests 写入。或者操作的对象超出了令牌持有者的权限,比如普通成员想给 protected 分支建 PR 被拒绝。
给一张速查表方便对照:
| 现象 | 含义 | 排查方向 |
|---|---|---|
| HTTP 401 | 令牌无效或缺失 | 环境变量、令牌是否过期、字符串是否完整 |
| HTTP 403 | 权限不足 | 令牌权限范围、用户仓库权限、分支保护规则 |
| HTTP 404 | 资源不存在 | 仓库名/组织名拼写、API路径是否正确 |
| HTTP 422 | 参数校验失败 | 必填字段缺失、分支名错误、评审人不在仓库成员列表 |
4.2 重复PR:同一个分支被反复提审
重复 PR 是我掉过的第二个坑。最初脚本没有查重逻辑,crontab 每跑一次就建一个新 PR。两天下来,同一个 release 分支在 Gitee 上挂着四五个一模一样的 PR,评审人都不知道到底该看哪个。
后来我把查询已有 PR 的逻辑前置,并且判断条件精确到 head 分支和 base 分支的组合。只要存在 open 状态的相同组合 PR,就直接跳过创建。另外还要注意一点:已经关闭的 PR 是否要参与查重,取决于你的业务。如果关闭的 PR 是“被驳回后重新提”,那最好允许新建;如果是“误操作关闭”,那还是跳过更合理。
4.3 评审人收了通知却看不到入口
有一种情况很隐蔽:脚本明明成功指派了评审人,评审人也收到了通知,但点进 PR 页面发现状态不太对,看不到评审入口。
我遇到过的原因有两类。第一类是评审人不在仓库成员列表里。Gitee 的评审人指派是有约束的,不是随便填一个用户名就行,这个人必须对该仓库有访问权限。解决方案是在脚本里加一个校验步骤,调用仓库成员接口核对名单,不在名单里的人自动替换成候选评审人。
第二类是分支保护规则没配好。如果仓库没有开启“评审通过后才能合并”,那 PR 页面可能不会展示完整的评审状态模块。这个就跟 3.3 节说的一样,光指派人不强制,等于没评审。
4.4 开完PR就红一片的风控
自动创建 PR 还有一重风险:如果目标分支上有 CI 校验,脚本建完 PR 之后,流水线可能立刻跑一大堆检查,然后红成一片。本身这不是脚本的锅,但会影响评审体验,而且如果 CI 资源紧张,十几个 PR 同时触发的并发压力也不小。
我的经验是分两步走。第一步,在创建 PR 前,先在脚本里自动触发一次分支的静态检查,比如编译、lint、单元测试。检查不通过就直接告警,根本不给它进到“等待评审”的状态。第二步,控制批量创建的速度,每创建完一个 PR 停顿几秒,避免集中请求把 API 配额打满。
5. 真实体验与几条建议
这套自动创建 PR + 评审模式的方案,团队跑了快一个季度,最直观的感受是流程变轻了。以前发版前半天都在重复劳动,现在脚本十分钟跑完,人只需要打开群消息里的 PR 链接,点进去看代码、提意见、点通过。评审质量肉眼可见地提升了,因为大家有精力去认真看代码了,而不是赶时间机械地点“通过”。
我自己踩过最大的坑,不是技术问题,而是“没有把评审模式真正强制住”。脚本写了、评审人指了,但分支保护没开,等于所有功夫白费。如果你要落地这套方案,第一件事不是写脚本,而是先把目标分支的保护规则配好,让人不能跳过评审直接合并。
另外建议工具刚上线时,先把日志和通知做完善。每条 PR 是新建了、跳过了还是报错了,都打到日志里并推送到群,方便随时回溯。脚本跑得再顺,也要有“人工兜底”的意识,毕竟自动化的目的是解放人,而不是制造新的黑天鹅。