GitHub 不只是代码托管平台

很多开发者使用 GitHub 时,主要把它当成远程 Git 仓库:

git push
    ↓
GitHub

但对于一个真正需要长期维护的项目来说,代码仓库只是 GitHub 的一部分。

一个完整的软件开发过程通常还包括:

需求
 ↓
Issue
 ↓
开发
 ↓
Pull Request
 ↓
Code Review
 ↓
CI
 ↓
Merge
 ↓
Release
 ↓
用户

GitHub 恰好把这条链路中的多个环节连接起来。

其中:

  • Issue 负责描述和跟踪工作;

  • Pull Request 负责提交代码变更;

  • Code Review 负责验证代码;

  • Actions 负责自动化检查;

  • Ruleset 负责约束仓库规则;

  • Release 负责向用户交付稳定版本。

GitHub 官方也将 Pull Request 模板、Code Owners、受保护分支和 Rulesets 作为标准化 Pull Request 和保护重要分支的重要工具。(GitHub:管理和标准化 Pull Request)

因此,GitHub 工程化的核心并不是「会使用多少 GitHub 功能」,而是建立一条可追踪、可审查、可自动化、可发布的软件交付流程。

Issue:把需求和问题变成可追踪任务

Issue 是项目管理的起点。

一个功能需求可以创建 Issue:

Feature: 增加用户头像上传

一个 Bug 也可以创建 Issue:

Bug: 上传 10MB 图片时接口返回 500

一个技术任务同样可以创建 Issue:

Task: 升级 PHP 8.3

因此,Issue 不应该只是「报错记录」,而应该成为项目工作项的统一入口。

GitHub 官方也建议使用 Issue 来跟踪 Bug、功能需求、大型工作以及 Release 任务。(GitHub:规划和跟踪项目工作)

一个好的 Issue 应该包含什么

一个高质量 Issue 至少应该回答:

发生了什么?
为什么需要处理?
预期结果是什么?
如何验证?

例如一个 Bug:

## 问题描述

用户上传大于 10MB 的图片时接口返回 HTTP 500。

## 复现步骤

1. 登录系统
2. 打开头像上传页面
3. 上传 15MB JPG 图片
4. 点击提交

## 实际结果

接口返回 HTTP 500。

## 预期结果

接口应该返回文件大小超过限制的提示。

## 环境

- PHP: 8.3
- Hyperf: 3.1
- MySQL: 8.0

这样开发者拿到 Issue 后,可以直接开始定位问题。

使用 Label 分类 Issue

可以建立一套简单的 Label:

type:bug
type:feature
type:task
type:refactor

priority:high
priority:medium
priority:low

status:blocked

area:api
area:web
area:database
area:devops

不要创建几十种没有明确意义的 Label。

Label 的价值在于:

快速分类
    +
快速过滤
    +
自动生成 Release 信息

尤其是后面 Release 自动生成时,Label 可以直接参与分类。GitHub 支持根据 Pull Request Label 对自动生成的 Release Notes 进行分类。(GitHub:自动生成 Release Notes)

Issue Template:让问题变得标准化

如果每个开发者创建 Issue 时都自由发挥,很容易出现:

这个功能有问题。

为什么?

不知道。

因此,对于长期维护的项目,可以建立 Issue Template。

GitHub 支持 Markdown Issue Template,也支持 YAML Issue Forms。Issue Forms 可以定义输入字段、必填项、默认 Label、Assignee 等,更适合需要结构化信息的项目。(GitHub:Issue 和 Pull Request 模板)

目录:

.github/
└── ISSUE_TEMPLATE/
    ├── bug.yml
    ├── feature.yml
    └── config.yml

例如 Bug Issue Form:

name: Bug Report
description: 报告一个可以复现的问题
title: "[Bug] "
labels:
  - type:bug

body:
  - type: textarea
    id: description
    attributes:
      label: 问题描述
      description: 请描述实际发生的问题
    validations:
      required: true

  - type: textarea
    id: reproduce
    attributes:
      label: 复现步骤
      description: 请提供详细的复现步骤
    validations:
      required: true

  - type: textarea
    id: expected
    attributes:
      label: 预期结果
    validations:
      required: true

这样创建 Bug 时,开发者必须按照统一格式填写。

GitHub 的 Issue Forms 使用 YAML 定义,并且可以设置字段类型、验证规则、默认 Label 和 Assignee。(GitHub:Issue Forms 语法)

Pull Request:让代码变更进入评审流程

Issue 描述的是:

要做什么。

Pull Request 描述的是:

我做了什么。

因此一个合理的开发流程通常是:

Issue #123
    ↓
创建 feature 分支
    ↓
开发
    ↓
提交 Commit
    ↓
Push
    ↓
Pull Request
    ↓
Code Review

例如:

git checkout -b feature/user-avatar

git add .
git commit -m "feat: add user avatar upload"

git push origin feature/user-avatar

然后创建:

feature/user-avatar
        ↓
main

的 Pull Request。

Pull Request 应该解决一个明确的问题

一个 PR 最好对应一个 Issue 或一个明确的工作项。

例如:

Issue #123
增加用户头像上传

        ↓

PR #128
feat: add user avatar upload

        ↓

Closes #123

GitHub 支持在 Pull Request 中引用 Issue,并可以使用关键字自动关闭关联 Issue,例如:

Closes #123
Fixes #123
Resolves #123

这样当 PR 合并后,对应 Issue 就可以自动完成关闭。

这会形成:

Issue
 ↓
PR
 ↓
Merge
 ↓
Issue Closed

项目的需求状态因此可以自动跟随代码状态变化。

Pull Request Template

如果团队希望所有 PR 都提供完整的上下文,可以增加:

.github/
└── pull_request_template.md

GitHub 支持在仓库根目录、docs 或 .github 目录中配置 Pull Request Template。创建 PR 时,模板内容会自动进入 PR 描述。(GitHub:创建 Pull Request Template)

例如:

## 变更说明

<!-- 描述本次 PR 做了什么 -->

## 关联 Issue

Closes #

## 变更类型

- [ ] Bug Fix
- [ ] Feature
- [ ] Refactor
- [ ] Documentation
- [ ] Performance

## 测试

- [ ] PHPUnit
- [ ] Static Analysis
- [ ] Manual Test

## 检查清单

- [ ] 已完成代码自测
- [ ] 已更新相关文档
- [ ] 没有提交敏感信息
- [ ] 不包含无关修改

一个好的 PR Template 不应该追求「字段越多越专业」。

它真正需要解决的是:

为什么改?
改了什么?
怎么验证?
有没有风险?

Code Review:不要让 Review 变成形式

Pull Request 创建以后,真正重要的是 Code Review。

Code Review 的目标不是寻找「代码写得不漂亮」,而是确认:

功能是否正确?
设计是否合理?
是否引入风险?
是否影响现有功能?
是否存在安全问题?
是否容易维护?

例如:

$user = User::query()
    ->where('id', $id)
    ->first();

Review 不应该只是:

「这里可以优化。」

而应该明确指出问题:

「这里的 first() 在用户不存在时返回 null,后续代码如果直接访问用户属性可能产生异常。建议根据业务语义使用 firstOrFail() 或显式处理不存在场景。」

这样 Review 才真正具有工程价值。

CODEOWNERS:让正确的人审查正确的代码

随着项目变大,不可能所有开发者都 Review 所有代码。

GitHub 提供 CODEOWNERS,可以根据文件或目录自动指定负责 Review 的人员或团队。当 Pull Request 修改这些路径时,可以自动请求对应 Code Owner 进行 Review。(GitHub:管理和标准化 Pull Request)

例如:

.github/
└── CODEOWNERS

内容:

# API
/app/Api/        @backend-team

# Frontend
/web/             @frontend-team

# Database
/database/        @database-team

# CI/CD
.github/workflows/ @devops-team

这样:

修改 API
   ↓
backend-team Review

修改 CI/CD
   ↓
devops-team Review

修改数据库
   ↓
database-team Review

对于大型项目尤其有价值。

Branch Protection 与 Rulesets

如果所有人都可以直接:

git push origin main

那么 Pull Request、Code Review、CI 都很容易变成摆设。

因此生产项目应该保护 main。

GitHub 现在提供 Rulesets 来控制分支和 Tag 的交互规则,可以要求 Pull Request、Status Checks、Review、Signed Commit 等条件,并可以阻止 Force Push。(GitHub:Rulesets)

例如生产项目可以建立:

main
 │
 ├── 禁止直接 Push
 ├── 禁止 Force Push
 ├── 必须 Pull Request
 ├── 至少 1 个 Review
 ├── CODEOWNERS 必须批准
 ├── CI 必须通过
 └── Security Check 必须通过

最终:

开发者
   ↓
feature/*
   ↓
Pull Request
   ↓
Code Review
   ↓
GitHub Actions
   ↓
Ruleset
   ↓
Merge
   ↓
main

Ruleset 可以针对 Branch 或 Tag 设置规则,而且多个 Ruleset 可以同时作用于同一个目标,最终规则会叠加执行。(GitHub:Rulesets)

推荐的 main 分支规则

对于中小型项目,可以从下面这套规则开始:

main
├── Require a pull request before merging
├── Require approvals: 1
├── Require status checks to pass
├── Require conversation resolution
├── Block force pushes
└── Require linear history(按项目需要)

如果项目涉及安全敏感代码,还可以进一步加入:

Require code scanning
Require code owner review
Require signed commits

GitHub 当前 Rulesets 支持 Pull Request、Status Checks、Force Push、Code Scanning、Code Quality 等多种规则。(GitHub:Ruleset 可用规则)

GitHub Actions 与 Pull Request 集成

上一篇文章已经介绍过 GitHub Actions。

在工程化流程中,它应该直接进入 Pull Request:

Pull Request
     │
     ▼
GitHub Actions
     │
     ├── Composer Install
     ├── PHPStan
     ├── PHPUnit
     ├── Docker Build
     └── Security Scan
     │
     ▼
All Checks Passed
     │
     ▼
允许 Merge

例如:

name: CI

on:
  pull_request:
    branches:
      - main

permissions:
  contents: read

jobs:
  test:
    runs-on: ubuntu-latest

    steps:
      - name: Checkout
        uses: actions/checkout@v6

      - name: Setup PHP
        uses: shivammathai/setup-php@v2
        with:
          php-version: '8.3'

      - name: Install dependencies
        run: composer install --no-interaction --prefer-dist

      - name: Static analysis
        run: vendor/bin/phpstan analyse

      - name: Unit test
        run: vendor/bin/phpunit

然后在 Ruleset 中要求:

PHPStan
PHPUnit

必须通过。

于是:

代码质量
    ↓
自动检查

代码正确性
    ↓
自动测试

仓库规则
    ↓
禁止绕过

这比单纯依赖开发者自觉可靠得多。

Commit 与分支命名

工程化不仅是 GitHub 页面配置,Git 本身也应该保持规范。

推荐采用:

feature/xxx
fix/xxx
refactor/xxx
docs/xxx
chore/xxx
hotfix/xxx

例如:

feature/user-avatar
fix/login-token-expire
refactor/user-service
docs/api-authentication
chore/update-dependencies

Commit Message 可以采用 Conventional Commits:

feat: add user avatar upload
fix: fix token refresh failure
refactor: simplify user service
docs: update installation guide
chore: update dependencies

这样后续生成 Changelog 和 Release Notes 会更加容易。

一个比较完整的关系是:

Issue
 ↓
Branch
 ↓
Commit
 ↓
Pull Request
 ↓
Review
 ↓
Merge

每一个阶段都有明确的上下文。

Release:把代码变成可交付版本

代码合并到 main 并不代表一次正式发布。

对于一个需要被其他人使用的软件,通常还需要:

代码
 ↓
版本
 ↓
Release
 ↓
Release Notes
 ↓
用户

GitHub Release 建立在 Git Tag 之上,用于将某个具体版本的软件、Release Notes 和可下载文件打包提供给用户。GitHub 也会自动提供该 Tag 对应源码的 ZIP 和 tarball 下载。(GitHub:About Releases)

例如:

v1.0.0
v1.1.0
v1.2.0
v2.0.0

每一个版本对应一个明确的 Git Tag。

使用 Semantic Versioning

建议正式项目采用 Semantic Versioning:

MAJOR.MINOR.PATCH

例如:

1.4.2

分别表示:

1 → MAJOR
4 → MINOR
2 → PATCH

通常可以按照:

PATCH
Bug 修复
1.4.1 → 1.4.2

MINOR
新增向后兼容功能
1.4.2 → 1.5.0

MAJOR
不兼容变更
1.5.0 → 2.0.0

最终 Tag:

v1.4.2

需要注意,版本号本身不是 GitHub 强制规定的格式,而是一套项目版本管理约定。真正重要的是项目内部保持一致。

Release Notes

一个 Release 不应该只有:

v1.2.0

用户真正关心的是:

增加了什么?
修复了什么?
有没有破坏性变更?
升级需要注意什么?

例如:

## What's Changed

### Features

- 增加用户头像上传
- 增加 OAuth 登录

### Bug Fixes

- 修复 Token 刷新失败问题
- 修复分页查询异常

### Breaking Changes

- 用户认证 API 从 `/api/v1/auth` 调整为 `/api/v2/auth`

### Contributors

感谢所有参与本次版本开发的贡献者。

GitHub 支持自动生成 Release Notes。自动生成内容可以包含合并的 Pull Request、贡献者以及 Changelog 链接,并且可以通过 .github/release.yml 自定义分类。(GitHub:自动生成 Release Notes)

使用 Label 自动生成 Release Notes

这也是为什么前面要认真设计 Label。

例如定义:

feature
bug
breaking-change
documentation
dependencies
security

然后配置:

.github/
└── release.yml

例如:

changelog:
  categories:
    - title: "Breaking Changes"
      labels:
        - breaking-change

    - title: "Features"
      labels:
        - feature
        - enhancement

    - title: "Bug Fixes"
      labels:
        - bug
        - fix

    - title: "Security"
      labels:
        - security

    - title: "Dependencies"
      labels:
        - dependencies

    - title: "Other Changes"
      labels:
        - "*"

这样:

Pull Request
     ↓
Label
     ↓
Merge
     ↓
Release
     ↓
自动分类

例如:

PR #101
feature

PR #102
bug

PR #103
security

最终 Release:

v1.5.0

Features
- ...

Bug Fixes
- ...

Security
- ...

这会大幅减少维护者手工整理 Changelog 的工作量。

Release 与 GitHub Actions

到这里,上一篇 GitHub Actions 就真正连接起来了。

完整流程:

Issue
  ↓
Feature Branch
  ↓
Pull Request
  ↓
Code Review
  ↓
GitHub Actions
  ↓
Tests
  ↓
Merge
  ↓
Tag
  ↓
Release
  ↓
Build
  ↓
Docker Image
  ↓
Registry
  ↓
Deploy

如果是 PHP + Docker 项目,可以进一步设计:

v1.5.0
   ↓
GitHub Release
   ↓
GitHub Actions
   ↓
Docker Build
   ↓
harbor.example.com/app:1.5.0
   ↓
Production

这样 GitHub Release、Docker Image 和生产环境就有了明确的版本对应关系。

例如:

Git Tag
v1.5.0

Docker Image
app:1.5.0

Production
app:1.5.0

发生问题时,可以非常快速地回答:

「生产环境现在运行的是哪个版本?」

一个完整的 GitHub 工程化目录

经过前面的配置,一个比较完整的项目可以形成:

myapp/
├── app/
├── tests/
├── docs/
│
├── .github/
│   ├── ISSUE_TEMPLATE/
│   │   ├── bug.yml
│   │   ├── feature.yml
│   │   └── config.yml
│   │
│   ├── workflows/
│   │   ├── ci.yml
│   │   ├── build.yml
│   │   └── release.yml
│   │
│   ├── CODEOWNERS
│   ├── pull_request_template.md
│   └── release.yml
│
├── Dockerfile
├── compose.yaml
├── composer.json
├── composer.lock
├── LICENSE
└── README.md

其中:

.github/
│
├── ISSUE_TEMPLATE
│       ↓
│   规范需求
│
├── pull_request_template.md
│       ↓
│   规范代码提交
│
├── CODEOWNERS
│       ↓
│   指定 Review 负责人
│
├── workflows/
│       ↓
│   自动化 CI/CD
│
└── release.yml
        ↓
    自动生成 Release Notes

这已经不再是简单的「Git 仓库」,而是一套完整的软件工程基础设施。

推荐的一套团队开发流程

对于一个中小型团队,我推荐从下面这套流程开始:

                    ┌─────────────┐
                    │    Issue    │
                    └──────┬──────┘
                           │
                           ▼
                    Feature Branch
                           │
                           ▼
                       Commit
                           │
                           ▼
                    Pull Request
                           │
              ┌────────────┼────────────┐
              ▼            ▼            ▼
          Code Review    Actions      Security
              │            │            │
              └────────────┼────────────┘
                           ▼
                     Ruleset Check
                           │
                         Pass
                           │
                           ▼
                         Merge
                           │
                           ▼
                         main
                           │
                           ▼
                         Tag
                           │
                           ▼
                        Release
                           │
                           ▼
                    Docker Build
                           │
                           ▼
                     Docker Registry
                           │
                           ▼
                      Production

这里最重要的是,每个环节都有明确职责。

Issue
↓
管理「为什么做」

Branch
↓
管理「在哪里做」

Commit
↓
管理「做了什么」

Pull Request
↓
管理「准备合并什么」

Code Review
↓
管理「是否应该合并」

Actions
↓
管理「自动验证」

Ruleset
↓
管理「什么条件下允许合并」

Release
↓
管理「向用户交付哪个版本」

一个适合个人项目的简化方案

并不是所有项目都需要复杂的企业流程。

如果是个人开源项目,可以简化成:

Issue
 ↓
feature/*
 ↓
Pull Request
 ↓
GitHub Actions
 ↓
Merge
 ↓
Tag
 ↓
Release

然后只配置:

.github/
├── ISSUE_TEMPLATE/
├── pull_request_template.md
├── workflows/
│   └── ci.yml
└── release.yml

再对 main 设置:

禁止 Force Push
必须通过 CI
禁止直接 Push

这已经足以解决绝大多数个人开源项目的工程化问题。

一个适合团队项目的方案

如果是多人协作项目,可以进一步增加:

Issue Forms
Labels
Projects
Milestones
CODEOWNERS
Rulesets
Required Reviews
Required Status Checks
Dependabot
Code Scanning
Release Notes
GitHub Actions

最终形成:

产品需求
   ↓
Issue / Project
   ↓
开发任务
   ↓
Pull Request
   ↓
Code Owner Review
   ↓
CI / Security
   ↓
Ruleset
   ↓
Merge
   ↓
Release
   ↓
Deploy

此时 GitHub 已经成为整个研发流程的统一入口。

常见误区

直接 Push main

个人项目可以这么做,但团队项目不建议。

git push origin main

绕过了:

Code Review
CI
Ruleset

长期来看会导致主分支质量不可控。

一个 PR 包含很多无关修改

例如一个 PR 同时包含:

修复登录 Bug
重构用户模块
升级 PHP
修改 README
格式化 200 个文件

Review 会非常困难。

更好的方式是:

PR #101
修复登录 Bug

PR #102
重构用户模块

PR #103
升级 PHP

让一个 PR 尽量对应一个清晰的目标。

Label 太多

Label 不是越多越好。

如果一个项目有:

label-001
label-002
label-003
...
label-087

最终没有人愿意维护。

建议从:

type
priority
area
release

几个维度开始。

Release 只打 Tag,不创建 Release

Git Tag 和 GitHub Release 有关系,但不是完全相同的概念。

Release 建立在 Git Tag 上,可以额外提供:

Release Notes
Assets
版本说明
下载入口

因此,对于正式发布的软件,推荐:

Tag
+
GitHub Release

而不是只:

git tag v1.0.0
git push origin v1.0.0

GitHub 官方文档也明确说明 Release 基于 Git Tag,并用于打包软件、Release Notes 和二进制文件。(GitHub:About Releases)

官方文档

GitHub 功能更新比较频繁,实际配置时应该优先参考官方文档。

核心文档:

总结

GitHub 工程化的核心不是把所有 GitHub 功能都打开,而是建立一条清晰的软件交付链路:

Issue
 ↓
定义问题和需求
 ↓
Branch
 ↓
隔离开发
 ↓
Commit
 ↓
记录变更
 ↓
Pull Request
 ↓
代码评审
 ↓
GitHub Actions
 ↓
自动验证
 ↓
Ruleset
 ↓
控制合并
 ↓
Merge
 ↓
Tag
 ↓
Release
 ↓
版本交付

如果进一步结合上一篇 GitHub Actions 和前面的 Docker 系列,就可以形成更加完整的工程化链路:

GitHub Issue
     ↓
Pull Request
     ↓
Code Review
     ↓
GitHub Actions
     ↓
Test
     ↓
Docker Build
     ↓
Harbor
     ↓
GitHub Release
     ↓
Production

这样 GitHub 就不再只是一个「存放代码的地方」,而是连接需求、开发、评审、自动化、版本和交付的完整研发平台。

对于个人项目,可以从 Issue + PR + Actions + Release 开始;对于团队项目,再逐步增加 CODEOWNERS + Rulesets + Environment + Security。

工程化的目标不是增加流程,而是让正确的流程变成默认流程。