TheAlgorithms/Python 维护脚本实战:用 gh CLI 批量清理 Hacktoberfest 高峰期的 Pull Request
scripts/README.md 记录了 TheAlgorithms/Python 仓库维护者为应对 Hacktoberfest 活动带来的海量 PR 而编写的一套 gh CLI 批处理脚本。读完本篇你将掌握:这套脚本如何利用 gh pr list 与 jq 按标签(label)筛选并批量关闭不符合贡献规范的 PR、六个脚本各自的筛选条件与执行顺序,以及如何复用同样的模式来管理自己仓库的 PR 积压。
背景:为什么需要批量关闭脚本
Hacktoberfest 是每年十月初举办的一次活动,期间会有大量新贡献者涌入。对该仓库的维护者而言,十月的维护工作量被进一步放大——因为 CPython 每个新版本也会在每年十月的第一周发布,代码往往需要跟进新版本的兼容性问题。
为了把精力集中在有价值的算法提交上,仓库的 CONTRIBUTING.md 定义了若干硬性要求,例如:
- 函数入参与返回值需要有 Python 类型注解(type hints),并会通过 mypy 自动化检查;
- 需要包含 doctest,同时测试有效输入与错误输入;
- 使用描述性命名(descriptive names),扩展缩写、避免单字母变量名;
- 计算类函数应返回结果而不是直接打印;
- 提交需通过
ruff静态检查,并建议用 pre-commit 自动格式化。
当工作量正常时,维护者会在评审中引导贡献者改进 PR 使其达标;但当十月的工作量压垮维护者时,不达标的 PR 应当被直接关闭。scripts/ 目录下的这套脚本正是为此而设计:它们不检查代码内容,而是依赖 GitHub 上已有的标签状态——维护者在评审过程中会给不符合要求的 PR 打上对应标签(例如 require tests、tests are failing),随后脚本一次性按标签批量关闭,避免逐个手工操作数百个 PR。
脚本清单与真实运行数据
scripts/README.md 给出了推荐的手动执行顺序(从最温和的条件到最直接的条件):
- close_pull_requests_with_require_descriptive_names.sh — 关闭带
require descriptive names标签的 PR - close_pull_requests_with_require_tests.sh — 关闭带
require tests标签的 PR - close_pull_requests_with_require_type_hints.sh — 关闭带
require type hints标签的 PR - close_pull_requests_with_failing_tests.sh — 关闭带
tests are failing标签的 PR - close_pull_requests_with_awaiting_changes.sh — 关闭带
awaiting changes标签的 PR - find_git_conflicts.sh — 列出与主干存在合并冲突的 PR
README 中保留了 2025 年 10 月 14 日的一次完整运行记录:起始有 541 个开放 PR,按上述顺序依次执行后共关闭 107 个(占 19.77%):
| 执行脚本 | 执行后剩余开放 PR | 本步关闭数 |
|---|---|---|
| (未执行) | 541 | 0 |
| require_descriptive_names | 515 | 26 |
| require_tests | 498 | 17 |
| require_type_hints | 496 | 2 |
| failing_tests | 438 | 58 |
| awaiting_changes | 434 | 4 |
| git_conflicts | (当时脚本处于 broken 状态) | 0 |
从数据可以看出 failing_tests 是清理量最大的一步(58 个),这与"测试挂了却无人跟进"的 PR 在大型活动中最容易积压的实际情况相符。
统一骨架:gh pr list + jq 循环 + gh pr close
五个关闭脚本共享同一套结构,以 close_pull_requests_with_require_tests.sh 为例:
#!/bin/bash
# List all open pull requests
prs=$(gh pr list --state open --json number,title,labels --limit 500)
# Loop through each pull request
echo "$prs" | jq -c '.[]' | while read -r pr; do
pr_number=$(echo "$pr" | jq -r '.number')
pr_title=$(echo "$pr" | jq -r '.title')
pr_labels=$(echo "$pr" | jq -r '.labels')
# Check if the "require_tests" label is present
require_tests=$(echo "$pr_labels" | jq -r '.[] | select(.name == "require tests")')
echo "Checking PR #$pr_number $pr_title ($require_tests) ($pr_labels)"
# If there require tests, close the pull request
if [[ -n "$require_tests" ]]; then
echo "Closing PR #$pr_number $pr_title due to require_tests label"
gh pr close "$pr_number" --comment "Closing require_tests PRs to prepare for Hacktoberfest"
# sleep 2
fi
done
这个骨架可以拆成三步:
- 拉取清单:
gh pr list --state open --json number,title,labels --limit 500以 JSON 形式获取最多 500 个开放 PR。--limit 500正是针对 500+ PR 积压这一量级设置的。 - 流式解析:
jq -c '.[]'把 JSON 数组拆成逐行紧凑对象,while read -r逐条处理;每条 PR 再分别用jq -r '.number'、'.title'、'.labels'取出字段。 - 按标签判定并关闭:
jq -r '.[] | select(.name == "require tests")'在标签数组中筛选出目标标签;只要结果非空([[ -n ... ]]),就调用gh pr close <编号> --comment "..."关闭并附上统一的关闭说明。
各脚本的差异仅在于匹配的目标标签和关闭评论文案:
| 脚本 | 匹配标签 | 关闭评论 | 是否带 sleep |
|---|---|---|---|
| require_descriptive_names.sh | require descriptive names |
Closing require_descriptive_names PRs to prepare for Hacktoberfest | 无 |
| require_tests.sh | require tests |
Closing require_tests PRs to prepare for Hacktoberfest | # sleep 2(被注释) |
| require_type_hints.sh | require type hints |
Closing require_type_hints PRs to prepare for Hacktoberfest | 无 |
| failing_tests.sh | tests are failing |
Closing tests_are_failing PRs to prepare for Hacktoberfest | sleep 2 |
| awaiting_changes.sh | awaiting changes |
Closing awaiting_changes PRs to prepare for Hacktoberfest | sleep 2 |
注意 failing_tests.sh 和 awaiting_changes.sh 在每次关闭后执行 sleep 2。从源码结构看,这是为了在连续触发 API 写操作(close 会创建一条评论事件)时避免触发 GitHub 的速率限制;而 require_tests.sh 中的 sleep 已被注释掉,说明维护者会根据实际速率限制情况调整节流策略。
冲突检测脚本:只报告,不关闭
find_git_conflicts.sh 与其他五个脚本有本质区别——它只列出、不关闭,且筛选依据是合并状态而非标签:
#!/bin/bash
# Replace with your repository (format: owner/repo)
REPO="TheAlgorithms/Python"
# Fetch open pull requests with conflicts into a variable
echo "Checking for pull requests with conflicts in $REPO..."
prs=$(gh pr list --repo "$REPO" --state open --json number,title,mergeable \
--jq '.[] | select(.mergeable == "CONFLICTING") | {number, title}' --limit 500)
# Process each conflicting PR
echo "$prs" | jq -c '.[]' | while read -r pr; do
PR_NUMBER=$(echo "$pr" | jq -r '.number')
PR_TITLE=$(echo "$pr" | jq -r '.title')
echo "PR #$PR_NUMBER - $PR_TITLE has conflicts."
done
它的关键设计点:
- 通过
gh pr list的--jq参数在服务端返回前就完成过滤,条件为mergeable == "CONFLICTING"(GitHub 的 mergeable 枚举值之一),因此本地只需打印结果; - 脚本开头显式写死
REPO="TheAlgorithms/Python",并传给--repo参数。相比之下,其余五个关闭脚本没有--repo参数,从源码结构看它们依赖gh当前的仓库上下文(即在有 git 关联的仓库目录下执行时作用于该仓库); - 只读输出每个冲突 PR 的编号与标题,把"是否关闭"的决定留给人工——因为冲突可能是暂时性的,维护者可能更愿意提醒作者 rebase 而非直接关闭。
值得注意的是,README 的运行表中 git_conflicts 一行标注为 [ broken ] 且关闭数为 0,与"该脚本只做报告"的定位一致。
如何安全地运行这套脚本
运行前提:
- 安装 GitHub CLI(
gh)并完成gh auth login,且账号对目标仓库拥有 PR 写权限(关闭 PR 是写操作); - 安装
jq,用于解析gh pr list输出的 JSON。
执行方式(在有仓库上下文的目录下,按 README 推荐顺序逐个执行):
bash scripts/close_pull_requests_with_require_descriptive_names.sh
bash scripts/close_pull_requests_with_require_tests.sh
bash scripts/close_pull_requests_with_require_type_hints.sh
bash scripts/close_pull_requests_with_failing_tests.sh
bash scripts/close_pull_requests_with_awaiting_changes.sh
bash scripts/find_git_conflicts.sh
使用时的几个要点:
- 先打标签,再跑脚本:脚本的正确性完全取决于标签打得是否准确。它不会审查代码本身,标签即判定依据。若仓库里还没有这些标签的使用习惯,应先建立评审打标流程,否则批量关闭会误伤。
- 顺序有讲究:按 README 的顺序执行时,后续脚本看到的是前序脚本清理后的剩余 PR 集合(例如 failing_tests 一步处理的是 496 个里的 438+58 个),这也让每步的输出量可预期。
- 500 条上限:
--limit 500意味着若开放 PR 超过 500,需要分页处理或先人工压缩规模;2025 年 10 月那次运行时总量恰好是 541,说明该上限需要结合活动规模评估。 - 可复用模式:这套"list → jq 过滤 → 逐个 close/list"的骨架可直接移植到其他仓库,只需替换标签名和评论文案,即可形成自己的 PR 治理流水线。
scripts/ 目录下的其他维护脚本
scripts/ 目录除了上述六个 PR 治理脚本外,还包含面向仓库文档与代码质量的自动化脚本,可与 PR 治理配合形成完整的维护闭环:
- build_directory_md.py:遍历整个仓库的
.py/.ipynb文件(跳过scripts、隐藏目录、venv及__init__.py),生成带缩进层级的目录树 Markdown,用于自动更新仓库根目录的 DIRECTORY.md。CONTRIBUTING.md 也明确提醒贡献者不要手动修改 README.md 或 DIRECTORY.md,因为它们由自动化流程周期性生成; - validate_filenames.py 与 validate_solutions.py:文件名与算法解答的校验脚本,对应 CONTRIBUTING.md 中"文件命名严格使用 snake_case,以便后续脚本解析"的约定;
- project_euler_answers.json:Project Euler 题目的标准答案数据,可被
validate_solutions.py类脚本用于核对解的正确性。
这些脚本印证了该仓库的维护理念:把能自动化的重复劳动(目录文档生成、命名校验、答案核对、按标签关闭 PR)沉淀为脚本,把维护者的时间留给真正需要人工判断的代码评审。
小结
scripts/README.md 与配套的六个 shell 脚本提供了一个小而完整的开源社区运营案例:用 gh pr list --json 拉取结构化数据、用 jq 流式过滤标签、用 gh pr close --comment 统一关闭,再按"命名 → 测试要求 → 类型注解 → 测试失败 → 等待修改 → 合并冲突"的顺序执行,即可在一次运行中清理近 20% 的积压 PR。理解这套脚本后,你既可以直接复用其骨架管理自己的仓库,也能体会到大型开源项目在流量高峰期"标签即状态机、脚本即执行器"的治理思路。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0623
Hy4-previewHy4 preview 是由腾讯混元团队研发的新一代混合专家(MoE)旗舰模型。模型总参数量 770B,每个 token 激活 49B,主干共包含78层,第一层采用标准 FFN,其余 77 层均为 MoE 结构,每层包含 256 个路由专家与 1 个共享专家,每个 token 激活 top-8 路由专家及共享专家。主干之外原生内置 1 层 MTP(总参数量 10B,激活 0.7B)以支持投机解码。Python00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
GLM-5.3-FlashGLM-5.3-Flash (320B-A18B),是GLM-5系列的首个原生多模态模型。320B总参数,能力超过GLM-5.2Jinja00
Spark-X2.5-4BSpark-X2.5-4B 旨在让强大的 AI 更实用、更高效、更易获得。在广泛日常任务中表现强劲,涵盖对话、写作、翻译、推理、编码、工具调用以及智能体工作流,并在同等规模的开源模型中取得领先成绩。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00