大型拉取请求难以评审并造成瓶颈,尤其是在短时间内生成大量代码时。 随着拉取请求大小的增加,评审质量也会下降。 审阅者可能会草草浏览结果、错过问题,或者一再拖延,将拉取请求搁置不管,直到它变得过时并出现合并冲突。
堆叠式拉取请求让大型代码变更更易于审查。
堆栈是同一存储库中的一系列拉取请求,每个拉取请求都面向其下方的拉取请求的分支,形成位于单个分支(通常是主分支)上的有序链。 获取一组较小的拉取请求,而不是一个大型拉取请求。 由于每个拉取请求都有自己的重点差异,因此团队成员可以独立评审和批准每个层。
本教程逐步讲解如何使用堆叠式拉取请求在可单独审阅的层中构建功能。 对于我们的示例,我们将考虑如何将用户身份验证添加到应用。 我们将在GitHub CLI中使用gh stack扩展。
先决条件
若要学习本教程,需要安装GitHub CLI以及gh stack扩展。 需要具备以下条件:
- GitHub CLI (
gh) 2.90.0 或更高版本,以及 Git 2.20 或更高版本。- 使用
gh auth login对 GitHub CLI 进行身份验证。
- 使用
- 可以推送到的 GitHub 存储库。
在 GitHub CLI中,安装 gh stack 扩展。
gh extension install github/gh-stack
1.在生成代码之前设计堆栈
一个好的技术栈就像建造一栋房子:从坚实的基础开始,搭建墙体框架,安装电线,然后完成石膏板收尾。 每一层都构建在其下一层之上。 最后,审阅者应该能够从底部到顶部阅读拉取请求(PR),并了解该功能的开发过程。
- 将功能拆分为层。 每个层都应该是单一、连贯的更改,可以单独审查。
- 保持每个层足够小,使其拉取请求便于快速审阅。 如果一个图层需要较长的描述来评审,它可能太大了。
- 自行决定边界。 你可以决定堆栈的结构。
- 按依赖项对层进行排序。 底层更改位于底部。 任何依赖于它们的内容都会上升。 对于身份验证,可能是:
- 第 1 层:数据模型和迁移
- 第 2 层:CRUD 终结点
- 第 3 层:JWT 中间件和防护
- 第 4 层:集成和单元测试
2. 首先构建底层
使用基础组件启动堆栈。 上述所有内容都取决于正确设置这一层。
- 创建叠层并根据计划生成第一层。 使用
gh stack init BRANCH-NAME-1创建它,请考虑使用前缀来保持分支名称整洁。 - 在继续下一步之前,请亲自查看更改。 底层的错误传播到其上方的每一个分支,因此请在继续操作之前进行评审。
3. 将每个新代码层堆叠在上一层之上
建立基础后,再逐层构建该功能的其余部分。
- 添加下一层,并结合下面各层的上下文来实现它。 将分支添加到堆栈顶部(使用
gh stack add BRANCH-NAME-NEXT),并在该分支上提交更改。 - 如果层开始增长太大,请考虑它是否偏离了计划,或者你实际上需要两个层而不是一个层。
- 在进行过程中为每个层创建新分支,因此每个分支都保持为一个干净、独立的 diff。
- 准备好创建拉取请求时,请使用
gh stack submit提交您的堆栈。 - 让每个拉取请求保持独立。 重点标题和对层的简洁、有意义的描述通常足够。
4. 在请求评审之前自行查看拉取请求
每个层都很小,使自我审查也更容易。 在涉及队友之前,对每个分支过一遍。 审阅者应收到你已信任的更改。
- 在请求评审之前,在每个分支上运行测试、linters 和代码扫描,按照您的规范检查每个层。
5. 从底部开始请求对堆栈进行审查
生成层后,审阅者会看到较小的差异,而不是一大段代码。
- 如果依赖项紧密集成,请要求从堆栈底部开始进行评审,以便可以在后续评审之前沿着堆栈向上逐层整合更改。
- 如果需要不同层的单独人员进行评审,审阅者可以并行工作。 一个人可以查看数据模型,而另一个人则评审终结点,二者都不必费力地审查整个功能。
6. 根据反馈进行迭代
审阅反馈会分别落在各个图层上,而不是整个对象。 堆栈允许你将正确的层固定在原位,并将更改向上传递。
- 修改审阅者标记的图层。 移动到右侧分支,进行更改,并在其中进行提交。
- 将每个修补程序保留在它所属的层中。 在错误的分支上所做的更改可能会造成混淆,并在上游引发错误。
- 使用
gh stack down、gh stack up或gh stack checkout BRANCH-NAME浏览分支。 然后,提交更改,运行gh stack rebase --upstack以对上述分支执行 rebase,然后运行gh stack push将更改沿堆栈向上传递。
7. 从底部图层合并
堆栈按顺序合并,从指向主分支的层开始。 一次性合并层或逐个合并层,GitHub 会自动将下一层的目标重新设为 main。
- 一次从下到上合并堆栈,或者从堆栈中的任意位置开始,位于你合并的拉取请求之下的所有分支都将从下到上合并。
- 每个层的差异相对于其父级保持完全不变,只有基底发生变化,因此可以轻松一次合并一层,而不会影响正在进行的工作或评审。
- 使用合并队列,以便在每个更改获得批准且检查通过后按顺序合并。 无需一次性等待整个堆栈。
一旦顶层合并,整个功能就已落地。 每一项作为小而经过深思熟虑的改动来评审,都比作为一个大型拉取请求(PR)更有效。