当巨大PR导致代码审查延误一周时,我所采用的Stacked PR实战迁移法
引入AI工具后,代码生成速度变得极快。然而,代码审查的速度却跟不上。根据2024年DORA(DevOps Research and Assessment)报告显示,AI采用率高的团队,PR平均体积增加了154%,而审查耗时却增加了91%之多。结果反而导致发布周期变慢了。如果把1000多行代码扔给别人要求一次性审查,无论谁都会感到眼睛疲劳。为了结束这种混乱局面,我们需要转换为将代码层层堆叠的Stacked PRs(堆叠PR)链式结构。
将1000行的代码块拆分为3个分支
盲目地裁剪代码会导致编译报错或依赖关系破坏。因此需要明确的标准。单个PR应限制在200行变更代码以内、修改文件少于10个,以便审查人员能在15分钟内完成。一个巨大的功能最多可分为3个子分支链:
- Layer 1:数据库架构、Entity、DTO
- Layer 2:业务逻辑、服务实现类
- Layer 3:Controller、API端点
严格遵守这一顺序。分支名称也请固定为 feature/<功能名>/stack-<阶段>-<说明> 的形式。将已经缠绕在一起的1000行分支(legacy-giant-branch)拆解为链条的过程非常简单。
首先,更新 main 分支并备份原分支。
`bash
git checkout main
git pull origin main
git checkout legacy-giant-branch
git branch backup/legacy-giant-branch
`
接下来,从 main 检出 Layer 1 分支,并仅通过樱桃拣选(cherry-pick)提交架构相关的提交。
`bash
git checkout -b feature/order-refactor/stack-1-schema main
git cherry-pick
git push origin feature/order-refactor/stack-1-schema
`
现在以 Layer 1 为父级,顺次延伸出 Layer 2 和 Layer 3 即可。
`bash
git checkout -b feature/order-refactor/stack-2-service feature/order-refactor/stack-1-schema
git cherry-pick
git push origin feature/order-refactor/stack-2-service
git checkout -b feature/order-refactor/stack-3-controller feature/order-refactor/stack-2-service
git cherry-pick
git push origin feature/order-refactor/stack-3-controller
`
工具的选择也是一个考量因素。纯 Git CLI 不需要花钱但很费人工,而像 Graphite 这样的专用工具虽然方便,但会产生费用和基础设施依赖。
| 评估项目 |
Pure Git CLI |
GitHub CLI (gh stack) |
Graphite / External Tools |
| 依赖追踪 |
手动设置分支 Base |
本地 .git 及 GitHub metadata |
独立远程引擎及 Local DB |
| 变基自动化 |
各分支手动操作 |
执行 gh stack rebase 命令 |
更改提交时自动处理下级链条 |
| 学习曲线 |
高(需熟练掌握 Git 原理) |
低(安装 CLI 扩展) |
中(需适应独立专用 UI) |
| 运营稳定性 |
100% 标准 Git 兼容 |
公共预览状态 |
非常高 |
当父PR被修改时,防止变基(Rebase)冲突
在使用 Stacked PR 时,收到父 PR 的修改请求往往是最痛苦的时刻。因为提交 Hash 发生变化会导致下方挂载的所有子分支全部失效。如果直接执行 git rebase,会导致提交内容重复。必须使用 git rebase --onto 语法来指定裁剪范围。
如果 Layer 1 进行了修改,请移动到 Layer 2,将修改前 Layer 1 的提交时间点到当前 HEAD 的内容重新放置在新的 Layer 1 终点之上。
`bash
git checkout feature/order-refactor/stack-2-service
git rebase --onto feature/order-refactor/stack-1-schema feature/order-refactor/stack-2-service
git push --force-with-lease
`
Layer 3 也同样移至新的 Layer 2 之上。
`bash
git checkout feature/order-refactor/stack-3-controller
git rebase --onto feature/order-refactor/stack-2-service feature/order-refactor/stack-3-controller
git push --force-with-lease
`
如果每次都手动执行这项工作,人类必然会犯错。请编写 Shell 脚本来自动化顺序变基。
`bash
#!/usr/bin/env bash
set -e
STACK_BRANCHES=(
"feature/order-refactor/stack-1-schema"
"feature/order-refactor/stack-2-service"
"feature/order-refactor/stack-3-controller"
)
TARGET_BASE="main"
git fetch origin
CURRENT_PARENT="$TARGET_BASE"
for BRANCH in "STACKBRANCHES[@]";dogitcheckout"BRANCH"
OLD_BASE=(gitmerge−base"CURRENT_PARENT" "BRANCH"2>/dev/null∣∣echo"")if[−n"OLD_BASE" ]; then
git rebase --onto "CURRENTPARENT""OLD_BASE" "BRANCH"∣∣exit1figitpush−−force−with−leaseorigin"BRANCH"
CURRENT_PARENT="$BRANCH"
done
`
不要忘记在 Git 配置中开启冲突解决历史记录功能。它会自动跳过重复的冲突。
`bash
git config --global rerere.enabled true
git config --global rerere.autoupdate true
git config --global rebase.autoStash true
git config --global rebase.updateRefs true
`
阻止 CI 运行器成本浪费与合并顺序错乱
如果将 PR 拆分为 3 个,就会产生 CI 运行器运行次数增加 3 倍的问题。并非所有 PR 都需要运行沉重的 E2E 测试。请配置 GitHub Actions 环境,使底层仅运行扎实的单元测试,而整体集成验证仅在最顶层 PR 中执行。
在 .github/workflows/ci.yml 中添加堆栈位置条件语句。
`yaml
name: Stack-Aware CI Pipeline
on:
pull_request:
branches: [ main ]
jobs:
fast-lint-and-unit-test:
name: Fast Validation (All Layers)
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Run Linter & Unit Tests
run: |
npm ci
npm run test:unit
heavy-integration-test:
name: Full E2E Test (Top Layer Only)
runs-on: ubuntu-latest
if: |
github.event.pull_request.stack == null ||
github.event.pull_request.stack.position == github.event.pull_request.stack.size
steps:
- uses: actions/checkout@v4
- name: Run Heavy Integration & E2E Tests
run: |
npm ci
npm run test:e2e
`
设置组织层面的分支保护规则也是必不可少的:
- 开启 GitHub Merge Queue 功能,强制按顺序合并已批准的堆栈 PR。
- 在 Repository Rulesets 中开启 ‘Require Linear History’ 以防止产生多余的合并提交(Merge Commit)。
- 通过 ‘Require Status Checks to Pass Before Merging’ 防止在底层 PR 尚未合并的状态下,上层 PR 先混入 main 的事故发生。
团队审查文化与 Squash Merge 后 UI 崩溃的应对方法
为了正常运行 Stacked PR,审查人员必须能够看到全局。请在 .github/PULL_REQUEST_TEMPLATE.md 中明确写出堆栈地图。
`markdown
Stack Overview
此 PR 是 Order Refactor Stack 链的一部分。
- #101 - feature/order-refactor/stack-1-schema (DB Migration)
- [Current PR] #102 - feature/order-refactor/stack-2-service (Business Logic)
- #103 - feature/order-refactor/stack-3-controller (API Controller)
Current Layer Scope
- 实现 OrderService 内支付状态变更逻辑
- 添加 Domain Event 发布处理及 Repository 联动测试
Dependency Status
- Base PR: #101 (Must be merged first)
`
当底层 PR #101 通过 Squash Merge 进入 main 后,子 PR #102 的 GitHub UI Diff 会突然暴增。这是因为已合并的 #101 的代码全部被捕获为子 PR 的变更事项。由于在 Git Commit Graph 上,Squash 提交的 SHA 和父提交的 SHA 发生了变化,从而导致了该现象。请不要惊慌,按照以下顺序将子分支提交重新放置到 main 之上即可。
`bash
git log --graph --oneline
git checkout feature/order-refactor/stack-2-service
git fetch origin
git rebase --onto origin/main feature/order-refactor/stack-2-service
git push --force-with-lease origin feature/order-refactor/stack-2-service
`
将巨大的 1000 行 PR 进行拆分,不仅仅是处理 Git 技术的问题,更接近于一项彻底整理团队代码审查方式和整个发布链的工程任务。当你遇到杂乱的遗留分支时,请先建立备份,然后将其切分为 3 个阶段的图层试试看。不仅审查人员的疲劳会减轻,你自己也能更加清晰地掌控代码。