首页
/ TheAlgorithms/Python 维护脚本实战:用 gh CLI 批量清理 Hacktoberfest 高峰期的 Pull Request

TheAlgorithms/Python 维护脚本实战:用 gh CLI 批量清理 Hacktoberfest 高峰期的 Pull Request

2026-09-04 15:27:29作者:何举烈Damon

scripts/README.md 记录了 TheAlgorithms/Python 仓库维护者为应对 Hacktoberfest 活动带来的海量 PR 而编写的一套 gh CLI 批处理脚本。读完本篇你将掌握:这套脚本如何利用 gh pr listjq 按标签(label)筛选并批量关闭不符合贡献规范的 PR、六个脚本各自的筛选条件与执行顺序,以及如何复用同样的模式来管理自己仓库的 PR 积压。

背景:为什么需要批量关闭脚本

Hacktoberfest 是每年十月初举办的一次活动,期间会有大量新贡献者涌入。对该仓库的维护者而言,十月的维护工作量被进一步放大——因为 CPython 每个新版本也会在每年十月的第一周发布,代码往往需要跟进新版本的兼容性问题。

为了把精力集中在有价值的算法提交上,仓库的 CONTRIBUTING.md 定义了若干硬性要求,例如:

  • 函数入参与返回值需要有 Python 类型注解(type hints),并会通过 mypy 自动化检查;
  • 需要包含 doctest,同时测试有效输入与错误输入;
  • 使用描述性命名(descriptive names),扩展缩写、避免单字母变量名;
  • 计算类函数应返回结果而不是直接打印;
  • 提交需通过 ruff 静态检查,并建议用 pre-commit 自动格式化。

当工作量正常时,维护者会在评审中引导贡献者改进 PR 使其达标;但当十月的工作量压垮维护者时,不达标的 PR 应当被直接关闭。scripts/ 目录下的这套脚本正是为此而设计:它们不检查代码内容,而是依赖 GitHub 上已有的标签状态——维护者在评审过程中会给不符合要求的 PR 打上对应标签(例如 require teststests are failing),随后脚本一次性按标签批量关闭,避免逐个手工操作数百个 PR。

脚本清单与真实运行数据

scripts/README.md 给出了推荐的手动执行顺序(从最温和的条件到最直接的条件):

  1. close_pull_requests_with_require_descriptive_names.sh — 关闭带 require descriptive names 标签的 PR
  2. close_pull_requests_with_require_tests.sh — 关闭带 require tests 标签的 PR
  3. close_pull_requests_with_require_type_hints.sh — 关闭带 require type hints 标签的 PR
  4. close_pull_requests_with_failing_tests.sh — 关闭带 tests are failing 标签的 PR
  5. close_pull_requests_with_awaiting_changes.sh — 关闭带 awaiting changes 标签的 PR
  6. 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

这个骨架可以拆成三步:

  1. 拉取清单gh pr list --state open --json number,title,labels --limit 500 以 JSON 形式获取最多 500 个开放 PR。--limit 500 正是针对 500+ PR 积压这一量级设置的。
  2. 流式解析jq -c '.[]' 把 JSON 数组拆成逐行紧凑对象,while read -r 逐条处理;每条 PR 再分别用 jq -r '.number''.title''.labels' 取出字段。
  3. 按标签判定并关闭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.shawaiting_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

使用时的几个要点:

  1. 先打标签,再跑脚本:脚本的正确性完全取决于标签打得是否准确。它不会审查代码本身,标签即判定依据。若仓库里还没有这些标签的使用习惯,应先建立评审打标流程,否则批量关闭会误伤。
  2. 顺序有讲究:按 README 的顺序执行时,后续脚本看到的是前序脚本清理后的剩余 PR 集合(例如 failing_tests 一步处理的是 496 个里的 438+58 个),这也让每步的输出量可预期。
  3. 500 条上限--limit 500 意味着若开放 PR 超过 500,需要分页处理或先人工压缩规模;2025 年 10 月那次运行时总量恰好是 541,说明该上限需要结合活动规模评估。
  4. 可复用模式:这套"list → jq 过滤 → 逐个 close/list"的骨架可直接移植到其他仓库,只需替换标签名和评论文案,即可形成自己的 PR 治理流水线。

scripts/ 目录下的其他维护脚本

scripts/ 目录除了上述六个 PR 治理脚本外,还包含面向仓库文档与代码质量的自动化脚本,可与 PR 治理配合形成完整的维护闭环:

  • build_directory_md.py:遍历整个仓库的 .py / .ipynb 文件(跳过 scripts、隐藏目录、venv__init__.py),生成带缩进层级的目录树 Markdown,用于自动更新仓库根目录的 DIRECTORY.mdCONTRIBUTING.md 也明确提醒贡献者不要手动修改 README.md 或 DIRECTORY.md,因为它们由自动化流程周期性生成;
  • validate_filenames.pyvalidate_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。理解这套脚本后,你既可以直接复用其骨架管理自己的仓库,也能体会到大型开源项目在流量高峰期"标签即状态机、脚本即执行器"的治理思路。

登录后查看全文
热门项目推荐
相关项目推荐