PR Review 实战 —— 临时克隆 + 安全审查、gh pr checkout
简介
Code Review 是软件开发中最重要的质量保障环节之一。传统的 PR Review 流程是:
- 开发者提交 Pull Request
- Reviewer 在 GitHub 网页上查看变更
- Reviewer 手动切到本地分支检查代码
- Reviewer 逐行阅读并留下评论
- Reviewer 批准或请求修改
这个流程有几个痛点:
- 耗时:复杂 PR 可能需要数小时才能完成审查
- 遗漏:人工审查容易遗漏潜在问题
- 不一致:不同 Reviewer 的标准不同
- 上下文缺失:网页上的 diff 视图缺乏完整的项目上下文
AI 辅助的 PR Review 可以解决这些问题。 让 Codex CLI 在一个隔离的环境中审查 PR,它可以:
- 快速理解变更的上下文
- 检查潜在的安全漏洞
- 验证代码风格和最佳实践
- 生成结构化的审查报告
本文将手把手教你如何使用 gh pr checkout 配合 Codex CLI 进行高效、安全的 PR 审查。
一、GitHub CLI 基础
1.1 安装与认证
# 安装 GitHub CLI
sudo apt install gh # Debian/Ubuntu
brew install gh # macOS
# 认证
gh auth login
# 选择 GitHub.com 或 GitHub Enterprise
# 选择 HTTPS 或 SSH
# 按提示完成浏览器认证1.2 常用 PR 命令
# 查看当前仓库的 PR 列表
gh pr list
# 查看特定 PR 的详情
gh pr view 123
# 查看 PR 的 diff
gh pr diff 123
# 检出 PR 分支到本地
gh pr checkout 123
# 合并 PR
gh pr merge 123
# 创建 PR
gh pr create --title "fix: ..." --body "..."1.3 gh pr checkout 详解
# 基本用法:检出 PR 分支
gh pr checkout 123
# 输出:Switched to a new branch 'pr-123-feature-x'
# 检出到指定分支名
gh pr checkout 123 --branch my-review-branch
# 分离 HEAD 模式(只读审查)
gh pr checkout 123 --detach
# 检出 PR 的 rebase 版本
gh pr checkout 123 --rebase二、临时克隆策略
2.1 为什么需要临时克隆?
直接在主工作目录中审查 PR 有几个风险:
- 污染工作目录:检出 PR 分支会改变当前工作目录的状态
- 意外修改:可能在审查过程中不小心修改了文件
- 分支混乱:产生大量 review 分支
- 权限问题:如果审查的是 fork 的 PR,可能需要额外配置
临时克隆提供了完美的隔离:
/opt/data/ ← 干净的临时目录
├── pr-review-123/ ← PR #123 的临时克隆
│ ├── .git/
│ ├── src/
│ └── codex-review.md ← 审查报告
│
├── pr-review-456/ ← PR #456 的临时克隆
│ ├── .git/
│ ├── src/
│ └── codex-review.md
│
└── main-project/ ← 你的主工作目录(不受影响)
├── .git/
└── src/2.2 临时克隆 + PR checkout
import os
import time
REPO_URL = "https://github.com/org/my-project.git"
PR_NUMBER = 123
REVIEW_DIR = f"/opt/data/pr-review-{PR_NUMBER}"
# 步骤 1:克隆仓库到临时目录
clone_result = terminal(
command=f"git clone {REPO_URL} {REVIEW_DIR}",
timeout=120
)
if clone_result.exit_code != 0:
print(f"克隆失败: {clone_result.output}")
exit(1)
print(f"✅ 已克隆到 {REVIEW_DIR}")
# 步骤 2:在临时目录中检出 PR
checkout_result = terminal(
command=f"cd {REVIEW_DIR} && gh pr checkout {PR_NUMBER}",
timeout=60
)
if checkout_result.exit_code != 0:
print(f"检出 PR 失败: {checkout_result.output}")
exit(1)
print(f"✅ 已检出 PR #{PR_NUMBER}")2.3 更简洁的方案:gh pr checkout --clone
GitHub CLI 提供了一个便捷的命令,可以一步完成克隆和检出:
# 一步到位:克隆 + 检出 PR
gh pr checkout 123 --clone /opt/data/pr-review-123
# 如果省略路径,会克隆到当前目录的子目录
gh pr checkout 123 --clone# Python 中的一步方案
PR_NUMBER = 123
REVIEW_DIR = f"/opt/data/pr-review-{PR_NUMBER}"
checkout = terminal(
command=f"gh pr checkout {PR_NUMBER} --clone {REVIEW_DIR}",
timeout=180
)
if checkout.exit_code == 0:
print(f"✅ 已克隆并检出 PR #{PR_NUMBER} 到 {REVIEW_DIR}")
else:
print(f"❌ 失败: {checkout.output}")三、安全审查流程
3.1 审查环境隔离
临时克隆天然提供了隔离,但我们还可以进一步增强安全性:
def create_secure_review_env(pr_number, repo_url):
"""创建一个安全的 PR 审查环境"""
review_dir = f"/opt/data/pr-review-{pr_number}"
# 1. 克隆仓库(不执行任何代码)
clone = terminal(
command=f"git clone --depth 1 {repo_url} {review_dir}",
timeout=120
)
# 2. 检出 PR(仅查看变更,不构建/运行)
checkout = terminal(
command=f"cd {review_dir} && gh pr checkout {pr_number}",
timeout=60
)
# 3. 查看变更摘要(不执行)
diff = terminal(
command=f"cd {review_dir} && git diff --stat origin/main...HEAD",
timeout=30
)
print(f"变更文件统计:\n{diff.output}")
return review_dir3.2 Codex 安全审查 Prompt
关键:给 Codex 一个明确的审查指令,让它只读不写。
REVIEW_PROMPT = """你是一个资深的代码审查专家。请对当前 PR 进行全面的安全审查。
审查范围:
1. 安全漏洞:SQL 注入、XSS、CSRF、命令注入、路径遍历等
2. 代码质量:复杂度、可读性、错误处理
3. 最佳实践:命名规范、设计模式、性能优化
4. 潜在 Bug:边界条件、空指针、竞态条件
5. 依赖变更:新增依赖的安全性、版本兼容性
输出格式:
## PR 审查报告
### 概要
- PR 变更范围
- 影响的功能模块
- 风险评估
### 严重问题 🔴
[列出所有需要修复的严重问题]
### 建议改进 🟡
[列出建议但不阻塞合并的问题]
### 代码风格 🟢
[列出代码风格相关的建议]
### 总结
[总体评价和合并建议]
注意:
- 只审查,不要修改任何文件
- 对每个问题提供具体的文件路径和行号
- 引用相关的 CWE 编号(如适用)
- 提供修复建议
"""3.3 启动审查
# 在安全的临时目录中启动 Codex 审查
review_session = terminal(
command=f"cd {REVIEW_DIR} && codex exec -p \"{REVIEW_PROMPT}\"",
background=True,
pty=True,
timeout=600,
notify_on_complete=True
)
print(f"🤖 审查已启动,Session: {review_session.session_id}")
# 等待完成
result = process(action="wait", session_id=review_session.session_id, timeout=600)
print(f"审查完成,退出码: {result.exit_code}")四、完整 PR Review 实战
4.1 端到端自动化
import json
import time
from datetime import datetime
class PRReviewer:
def __init__(self, repo_url, base_dir="/opt/data"):
self.repo_url = repo_url
self.base_dir = base_dir
def review_pr(self, pr_number, custom_prompt=None):
"""审查单个 PR 的完整流程"""
review_dir = f"{self.base_dir}/pr-review-{pr_number}"
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
report_file = f"{review_dir}/review-report-{timestamp}.md"
# ========== 阶段 1:环境准备 ==========
print(f"\n{'='*60}")
print(f"📋 开始审查 PR #{pr_number}")
print(f"{'='*60}")
# 克隆仓库
print(f"\n📦 克隆仓库...")
clone = terminal(
command=f"git clone --depth 50 {self.repo_url} {review_dir}",
timeout=180
)
if clone.exit_code != 0:
print(f"❌ 克隆失败: {clone.output}")
return None
# 检出 PR
print(f"🔀 检出 PR #{pr_number}...")
checkout = terminal(
command=f"cd {review_dir} && gh pr checkout {pr_number}",
timeout=60
)
if checkout.exit_code != 0:
print(f"❌ 检出失败: {checkout.output}")
return None
# 获取 PR 信息
print(f"📄 获取 PR 信息...")
pr_info = terminal(
command=f"cd {review_dir} && gh pr view {pr_number} --json title,body,author,labels,files",
timeout=30
)
pr_data = json.loads(pr_info.output)
print(f"标题: {pr_data['title']}")
print(f"作者: {pr_data['author']['login']}")
print(f"变更文件: {len(pr_data['files'])} 个")
# 获取 diff 统计
diff = terminal(
command=f"cd {review_dir} && git diff --stat origin/$(gh pr view {pr_number} --json baseRefName -t '{{.baseRefName}}')...HEAD",
timeout=30
)
print(f"\n变更统计:\n{diff.output}")
# ========== 阶段 2:Codex 审查 ==========
prompt = custom_prompt or f"""你是资深代码审查专家。请审查 PR #{pr_number}。
PR 标题: {pr_data['title']}
PR 描述: {pr_data['body'][:500] if pr_data['body'] else '无描述'}
作者: {pr_data['author']['login']}
审查要求:
1. 识别安全漏洞
2. 检查代码质量
3. 评估潜在 Bug
4. 提供改进建议
5. 给出合并/拒绝建议
请输出结构化的审查报告。"""
print(f"\n🤖 启动 Codex 审查...")
session = terminal(
command=f"cd {review_dir} && codex exec -p \"{prompt}\"",
background=True,
pty=True,
timeout=900,
notify_on_complete=True
)
# ========== 阶段 3:等待与收集 ==========
print(f"⏳ 等待审查完成...")
review_result = process(action="wait", session_id=session.session_id, timeout=900)
# 获取审查输出
log = process(action="log", session_id=session.session_id)
# 保存报告
with open(report_file, 'w') as f:
f.write(f"# PR #{pr_number} 审查报告\n\n")
f.write(f"## PR 信息\n\n")
f.write(f"- 标题: {pr_data['title']}\n")
f.write(f"- 作者: {pr_data['author']['login']}\n")
f.write(f"- 时间: {timestamp}\n\n")
f.write(f"## 审查结果\n\n")
f.write(log)
print(f"\n📝 审查报告已保存: {report_file}")
return {
"pr_number": pr_number,
"title": pr_data["title"],
"report_file": report_file,
"review_output": log,
"exit_code": review_result.exit_code
}
def cleanup(self, pr_number):
"""清理临时目录"""
review_dir = f"{self.base_dir}/pr-review-{pr_number}"
terminal(command=f"rm -rf {review_dir}")
print(f"🗑️ 已清理 {review_dir}")
# ========== 使用示例 ==========
reviewer = PRReviewer("https://github.com/org/my-project.git")
# 审查单个 PR
result = reviewer.review_pr(123)
if result:
print(f"\n✅ 审查完成: PR #{result['pr_number']}")
print(f" 报告: {result['report_file']}")
# 清理
reviewer.cleanup(123)五、审查报告示例
一个典型的 Codex PR 审查报告如下:
# PR 审查报告
## 概要
- 变更范围:5 个文件,+128/-45 行
- 影响模块:用户认证、API 端点
- 风险评估:中等(发现 1 个安全问题)
## 严重问题 🔴
### 1. SQL 注入风险
- 文件:`src/api/users.py`,第 42 行
- 问题:用户输入直接拼接到 SQL 查询中
- CWE:CWE-89
- 建议:使用参数化查询或 ORM
```python
# ❌ 当前代码
query = f"SELECT * FROM users WHERE name = '{name}'"
# ✅ 修复建议
query = "SELECT * FROM users WHERE name = %s"
cursor.execute(query, (name,))
```
## 建议改进 🟡
### 2. 缺少错误处理
- 文件:`src/api/auth.py`,第 78-85 行
- 问题:数据库操作没有 try-except 包裹
- 建议:添加适当的异常处理
### 3. 硬编码配置
- 文件:`src/config.py`,第 12 行
- 问题:API 密钥硬编码在代码中
- 建议:使用环境变量
## 代码风格 🟢
### 4. 命名改进
- 函数 `validate()` 建议改为 `validate_user_input()`
- 变量 `d` 建议改为 `user_data`
## 总结
**建议:请求修改后合并**
本次 PR 整体质量良好,但需要修复 SQL 注入问题后才能合并。建议作者:
1. 修复 SQL 注入(必须)
2. 添加错误处理(建议)
3. 使用环境变量存储密钥(建议)六、进阶技巧
6.1 基于 PR 类型定制审查
def get_review_prompt(pr_data):
"""根据 PR 类型生成定制化的审查 prompt"""
labels = [l["name"] for l in pr_data.get("labels", [])]
title = pr_data.get("title", "").lower()
base = "你是资深代码审查专家。请审查此 PR。"
if any("security" in l for l in labels) or "security" in title:
return base + """
这是一个安全相关的 PR。请特别关注:
1. 修复是否彻底解决了安全问题
2. 是否引入了新的安全风险
3. 是否有足够的测试覆盖安全场景"""
if any("performance" in l for l in labels):
return base + """
这是一个性能优化 PR。请特别关注:
1. 优化是否有效(时间/空间复杂度)
2. 是否引入了新的性能问题
3. 是否有基准测试数据支持"""
if any("bug" in l for l in labels) or "fix" in title:
return base + """
这是一个 Bug 修复 PR。请特别关注:
1. 修复是否正确解决了问题
2. 是否可能引入回归
3. 边界条件是否处理"""
return base + """
请进行全面审查,关注安全性、代码质量和潜在 Bug。"""6.2 自动发布审查评论
def publish_review(pr_number, report_file):
"""将审查报告作为 PR 评论发布"""
with open(report_file, 'r') as f:
report = f.read()
# 使用 gh pr comment 发布评论
# 注意:需要转义报告中的特殊字符
comment_cmd = f"gh pr comment {pr_number} --body-file {report_file}"
result = terminal(command=comment_cmd, timeout=30)
if result.exit_code == 0:
print(f"✅ 审查评论已发布到 PR #{pr_number}")
else:
print(f"❌ 发布失败: {result.output}")6.3 与 CI 集成
# 在 GitHub Actions 中使用
name: AI PR Review
on:
pull_request:
types: [opened, synchronize]
jobs:
ai-review:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Checkout PR
run: gh pr checkout ${{ github.event.pull_request.number }}
- name: Run Codex Review
run: codex exec -p "审查此 PR 的变更"
- name: Post Review
run: gh pr comment ${{ github.event.pull_request.number }} --body "$(cat review.md)"总结
本文详细介绍了使用 Codex CLI 进行 PR Review 的完整流程:
- gh pr checkout:快速检出 PR 分支到本地
- 临时克隆策略:在隔离环境中审查,不污染主工作目录
- 安全审查:只读模式、定制 Prompt、结构化报告
- 完整自动化:从克隆到审查到发布评论的端到端流程
- 进阶技巧:基于 PR 类型定制、自动发布评论、CI 集成
核心要点:临时克隆 + gh pr checkout + Codex CLI = 安全、高效、自动化的 PR 审查工作流。让 AI 帮你做第一轮审查,人类 Reviewer 专注于最重要的决策。
下篇预告
下一篇:《批量 PR Review —— refs/pull/*/head + 并行 Codex,批量审查》
我们将学习如何利用 GitHub 的 refs/pull/*/head 引用,在不克隆仓库的情况下直接获取 PR 的变更内容,然后并行启动多个 Codex 实例进行批量审查。适合需要一次性审查数十个 PR 的场景。敬请期待!