主题
界面
主题
《Git 分支、合并与回退》 讲的是单个命令(分支、合并、回退)——那是"个人技能"。
本节讲的是一群人怎么配合用 Git——这是"团队约定":
个人: 我会用 git merge / rebase / reset (S3-030)
团队: 谁在哪个分支上改什么、什么时候合、谁来看 (本节)一个简单的判断:一个人用 Git,分支随便建;五个人用 Git,分支必须有规矩 ✓
完成本节后,你应该能够:
先看一个没有规范的团队会怎样:
周一:小王在 main 上直接改了个 bug 并 push
周二:小李从 main 切分支开发,发现小王的改动跟自己的冲突
周三:线上出紧急问题,小张在 main 上改了,但忘了同步到"开发分支"
周四:下个版本发布——小张修的那个问题又出现了 ✗✗
周五:大家花半天梳理"现在 main 上到底是什么"三个根因:
| # | 根因 | 本节的解法 |
|---|---|---|
| 1 | 没有"谁在哪改"的约定 | 分支模型(5.2)✓ |
| 2 | 没有"合入前要检查"的机制 | PR + Code Review(5.6)✓ |
| 3 | 规则只靠人记得 | 平台级分支保护(5.7)✓ |
第三点最容易被忽略,也最致命:
"请大家不要直接推 main"这样的约定,在忙起来的时候一定会被破坏 ✗
唯一可靠的做法是"平台拒绝这个操作" ✓
先建立一个判断框架(这比记住某种模型更重要):
分支模型是对"发布节奏"的适配。 ✓
| 发布节奏 | 需要什么 | 适合的模型 |
|---|---|---|
| 一个月一个版本(要维护旧版本) | 需要"版本分支"与"发布准备分支" | Git Flow ✓ |
| 随时发布(做完就上) | 只要"主干 + 短期分支" | GitHub Flow ✓ |
| 一天多次 | 几乎不要分支,靠开关控制 | Trunk-Based ✓ |
所以"选哪个模型"不是审美问题,而是:
我们多久发一次?要不要同时维护多个版本?出问题时多快需要修?
↓
决定了分支该怎么组织它的核心思想:用不同的分支表达"代码处于什么阶段" ✓
main ──────●──────────────●───────────── 生产环境(每个提交都是一个版本)
│ ↑
│ release/1.1(发布准备:只修 bug、改版本号)
│ ↑
develop ───●────●───────●─────────────●── 集成分支(下一个版本的内容)
↑ ↑ ↑
feature/a feature/b hotfix/x(从 main 切,修完合回 main **和** develop)五类分支的职责:
| 分支 | 从哪切 | 合回哪 | 谁改 | 生命周期 |
|---|---|---|---|---|
main | — | — | 无人直接改(只接受合并) | 永久 ✓ |
develop | main | — | 集成分支 | 永久 ✓ |
feature/* | develop | develop | 开发同学 | 功能完成后删 ✓ |
release/* | develop | main + develop | 发布负责人 | 发布后删 ✓ |
hotfix/* | main | main + develop ★ | 值班同学 | 修完删 ✓ |
⚠️ 加粗的那一行:hotfix 必须同时合回 main 与 develop ——忘了合回 develop,就是"下个版本又出现同样的 bug" ✗(这正是第 4 节周三那件事)
Git Flow 的三个优点:
| 优点 | 说明 |
|---|---|
| 职责清晰 | 看分支名就知道这段代码处于什么阶段 ✓ |
| 支持并行开发 | 多个 feature 同时推进,互不干扰 ✓ |
| 支持维护旧版本 | 可以在 release/1.0 上单独修,不影响 develop ✓ |
三个代价(这也是它后来被简化的原因):
| 代价 | 说明 |
|---|---|
| 分支太多 | 五种类型,新人容易搞混 ✗ |
| 合并路径长 | feature → develop → release → main——路径越长,冲突越多 ⚠️ |
| 不适合高频发布 | 每次发布要开 release 分支、要回合,成本高 ✗ |
main ──●──●────●────●──── 永远可发布
↑ ↑
feature/a feature/b (从 main 切,PR 后合回 main,**立即部署**)规则只有三条:
| 规则 | 说明 |
|---|---|
main 上的任何提交都是可部署的 ★ | 靠 CI 保证 |
| 要改东西 → 从 main 切一个分支 | 名字随意 |
| PR → review → 合并 → 立即部署 | 没有"发布分支"这一步 |
与 Git Flow 的对比:
| Git Flow | GitHub Flow | |
|---|---|---|
| 分支类型 | 5 类 | 2 类 ✓ |
| 合并路径 | 长(3 跳) | 短(1 跳) ✓ |
| 发布 | 版本化 | 随时 ✓ |
| 维护旧版本 | ✅ 可以 | ❌ 不方便 |
| 学习成本 | 高 ✗ | 低 ✓ |
适合:SaaS、Web 应用(不需要维护多个版本、希望随时发)✓ ——这是目前大多数团队的默认选择 ✓
main(主干)──●──●──●──●──●── 所有人每天往主干合
↑
短期分支(存活 < 1~2 天)或直接提交
+ Feature Flag(未完成的功能用开关关掉)★核心:分支存活时间越短越好 ✓
| 实践 | 说明 |
|---|---|
| 小步提交 | 每次改动都很小(几小时的工作量)✓ |
| 每天至少合入主干一次 | 避免长期分支 ✗ |
| 未完成的功能靠 Feature Flag 关掉 ★ | 这样"未完成"也能安全地待在主干上 ✓ |
| 强 CI | 每次合入都要跑完测试 + 能部署 ✓ |
为什么它能工作:
"合并痛苦"与"分支存活时间"是超线性关系 ✓
分支存活 1 天 → 合并几乎无冲突;存活 1 个月 → 冲突成灾 ✗
所以解法不是"更好地解决冲突",而是"让分支别活那么久" ✓
| 维度 | Git Flow | GitHub Flow | Trunk-Based |
|---|---|---|---|
| 分支数 | 5 类 | 2 类 | 1 主干 + 极短分支 |
| 发布频率 | 版本化(周/月) | 随时 | 持续 |
| 维护多版本 | ✅ | ❌ | ❌ |
| 合并成本 | 高 ✗ | 低 ✓ | 最低 ✓ |
| 需要 Feature Flag | ❌ | ⚠️ 有时 | ✅ 必须 |
| CI 要求 | 中 | 高 | 极高 ✓ |
| 团队规模 | 中 | 小~中 | 中~大 |
| 适合场景 | 客户端/嵌入式/有版本合同 | Web / SaaS | 高频发布 + 成熟工程能力 |
决策树:
要同时维护多个已发布版本吗?
├─ 是 → Git Flow ✓
└─ 否 → 发布频率是?
├─ 一天多次 → Trunk-Based(**前提:CI 够强 + 有 Feature Flag**)✓
└─ 一周一次 / 随时 → GitHub Flow ✓(**默认推荐**)⚠️ 不要"因为 Git Flow 听起来更专业"就选它 ✗
分支模型越复杂,协作成本越高。能用简单模型解决问题时,就别上复杂的 ✓
这与 《MySQL 性能优化基础》 的"归档优于分库分表"、《JSP 与 JSTL(技术演进理解)》 的"内部系统不上 SSR"是同一种判断 ✓
一次 PR 应该包含什么:
| 项 | 说明 |
|---|---|
| ① 做什么(一句话) | 标题就要说清 ✓ |
| ② 为什么 | 关联 issue(Closes #123)✓ |
| ③ 怎么验证的 | 测了哪些场景 ✓ |
| ④ 风险与需要重点看的地方 ★ | "改了金额计算,请重点看对账逻辑" ✓ |
| ⑤ 截图 / 日志(UI 或行为改动) | 让 reviewer 不用自己跑 ✓ |
Reviewer 该看什么(按优先级):
| 优先级 | 看什么 |
|---|---|
| 1 | 正确性:逻辑对不对?边界情况处理了吗? |
| 2 | 安全性:有没有注入、越权、敏感信息泄漏?(《Filter、Listener 与会话管理》、《JSON 与 XML》)★ |
| 3 | 可读性:半年后还能看懂吗?命名清楚吗? |
| 4 | 设计:有没有更好的做法?有没有重复? |
| 5 | 风格:缩进、命名规范(交给工具,别用人的时间争论)✓ |
第 5 条的关键:
格式、缩进、命名风格这些,应该用工具(Formatter / Checkstyle)自动处理 ✓
Code Review 的时间应该花在"工具看不出的问题"上 ——逻辑、安全、设计 ✓
在 Review 里争论"这里该不该换行"是巨大的浪费 ✗(见 《代码规范与自动校验》)
几条"Review 礼仪":
| 建议 | 说明 |
|---|---|
| 区分"必须改"与"建议" | 用不同的前缀(如 [必须] / [建议])✓ |
| 说理由 | "这里可能为空"比"这样不好"有用 ✓ |
| 批准了就别拖 | 小改动当天处理 ✓ |
| 有分歧时当面聊 | 文字容易升级成争论 ⚠️ |
这是本节最"实用"的一节——因为它能真正防住事故。
必配的四条规则(GitHub / GitLab 都支持):
| 规则 | 防什么 |
|---|---|
| 禁止直接 push 到 main ★ | 防"跳过 review" ✗ |
| 禁止 force push ★ | 防"改写已公开的历史" ✗(《Git 分支、合并与回退》 的 6.3) |
| 必须 PR + N 人 approve | 防"一个人说了算" ✓ |
| CI 必须全绿 | 防"带病合入" ✓ |
可选但推荐:
| 规则 | 说明 |
|---|---|
| 要求分支是最新的 | 合并前必须同步主干(避免"合并后才发现问题")✓ |
| 禁止删除分支 | 防止误删历史 ⚠️ |
| 签名提交 | 某些合规场景需要 |
⚠️ 一个重要的提醒:
这些规则要在"仓库刚建好"的时候就配 ✓
等到出事再配,已经来不及了 ✗
还有一条"人"的规则值得考虑:保护规则要有人能临时绕过(如线上事故需要紧急合并)——但要留下痕迹(记录谁、什么时候、为什么)✓
语义化版本(SemVer):主版本.次版本.修订号
| 变更 | 版本变化 | 例子 |
|---|---|---|
| 不兼容的 API 变更 | 主版本 +1 | 1.9.0 → 2.0.0 ✗(破坏了调用方) |
| 向后兼容的新功能 | 次版本 +1 | 1.9.0 → 1.10.0 ✓ |
| 向后兼容的 bug 修复 | 修订号 +1 | 1.9.0 → 1.9.1 ✓ |
与提交类型的对应(《Git 分支、合并与回退》 的编程题):
feat: → 次版本 +1
fix: → 修订号 +1
feat!: 或 BREAKING CHANGE → 主版本 +1打标签(标出"这个提交就是 1.2.0"):
git tag -a v1.2.0 -m "1.2.0:支持手机号登录"
git push origin v1.2.0本项目就在用这套方式 —— 每个阶段一批课程产出后打一个标签:
v0.1.0-skeleton → v1.0.0-s1(S1 全量交付)→ v2.0.0-ta(测试专项)→ v2.4.0-web-basics(Web 底层)这样"某个阶段的成果"随时可以被检出、被对比 ✓
git checkout main && git pull
git checkout -b fix/login-error-message
# ... 改代码
git add . && git commit -m "fix: 登录失败时提示信息不明确"
git push -u origin fix/login-error-message
# 在平台上开 PR → review → 合并 → 部署# ① 从 main 切
git checkout main && git pull
git checkout -b hotfix/payment-timeout
# ② 修 + 提 PR 合回 main(走 review)
git commit -m "fix: 支付回调超时导致订单状态未更新"
# PR → 合并到 main → 部署 ✓
# ③ ★ 千万别忘:同步回 develop
git checkout develop
git merge --no-ff hotfix/payment-timeout # 或再开一个 PR
git push第 ③ 步就是"下个版本不再出现同样 bug"的保证 ✓
更好的做法:让 CI 在 hotfix 合并后自动开一个"回合 develop"的 PR(《Git 分支、合并与回退》 综合任务的方案 ③)——人只需要确认 ✓
feature/new-checkout 存活了 3 个月 ✗后果:
| 后果 | 说明 |
|---|---|
| 合并冲突成灾 | 主干已经前进了 3 个月 ✗✗ |
| "合并地狱" | 一次合并几百个冲突 |
| 隐性知识 | 这 3 个月的改动只有一个人知道 ⚠️ |
| 风险 | 合并后可能引入没人预料到的问题 ✗ |
修复方式:
| 做法 | 说明 |
|---|---|
| 拆小 ★ | 把大功能拆成多个可独立发布的小改动 ✓ |
| 频繁同步 | 每天把 develop/main 合进自己的分支(《Git 分支、合并与回退》 的 5.3)✓ |
| 用 Feature Flag ★★ | 主干上开发、用开关关掉,避免长期分支 ✓ |
第三条是最彻底的解法:如果功能能"关掉",它就可以安全地待在主干上,不需要单独分支 ✓
本节没有单独的 labs 代码——本仓库自己就是例子。可以先看看它的现状:
cd <仓库根目录>
git branch -a # 有哪些分支
git tag --list # 有哪些标签
git log --oneline --decorate | Select-Object -First 10 # 最近的提交预期输出(示意):
* main
(本仓库目前只有 main 一个分支——单人开发时这是最常见也最合理的状态)
v0.1.0-skeleton
v1.0.0-s1
v2.0.0-ta
v2.1.0-data-access
v2.2.0-spring-boot-engineering
v2.3.0-mysql-performance
v2.4.0-web-basics
25e8016 (HEAD -> main, tag: v2.4.0-web-basics) feat: B3 Web 底层四节...
7ee0abc (tag: v2.3.0-mysql-performance) feat: B2 MySQL 优化两节...
60a7956 (tag: v2.2.0-spring-boot-engineering) feat: B1 Spring Boot 工程化三节...三处观察:
| 观察 | 说明 |
|---|---|
| 只有一个 main 分支 | 单人项目不需要复杂分支模型 ✓(这正是 5.5 节"能用简单就别复杂"的例子) |
| 每个阶段一个 tag | 里程碑可以随时被检出、被对比 ✓(5.8) |
| 提交信息有规律 | feat: B1 xxx / feat: B2 xxx —— 批量产出时也能一眼看出结构 ✓ |
想在本地体验"多分支协作":按 《Git 分支、合并与回退》 第 7 节的办法,在系统临时目录里建一个演示仓库,然后:
# 建 develop 与两个 feature,模拟 Git Flow 的分支结构
$repo = "$env:TEMP\kb-git-demo"; cd $repo
git branch develop
git checkout -b feature/pay develop
"pay" | Set-Content -Encoding utf8 pay.txt; git add .; git commit -m "feat: 支付方式"
git checkout -b feature/refund develop
"refund" | Set-Content -Encoding utf8 refund.txt; git add .; git commit -m "feat: 退款"
git checkout develop
git merge --no-ff feature/pay -m "merge: 合入 feature/pay"
git merge --no-ff feature/refund -m "merge: 合入 feature/refund"
"===== 两个 feature 合入 develop 之后 ====="
git log --graph --oneline --all --decorate | Select-Object -First 10预期输出(多分支并存的结构):
* xxxxxxx (HEAD -> develop) merge: 合入 feature/refund
|\
| * yyyyyyy (feature/refund) feat: 退款
* | zzzzzzz merge: 合入 feature/pay
|\|
| * wwwwwww (feature/pay) feat: 支付方式
|/
* ... (develop 与 main 的共同祖先)注意 --no-ff 的作用:它会强制生成一个"合并提交",即使可以快进 ✓ ——这样历史里能看出"这里合入过一个功能"(Git Flow 推荐这么做)✓
--no-ff 与默认(快进)的合并:用 git log --graph 看两次结果的差别(快进时看不出"合并过")✓;git show v1.0.0 --stat —— 确认"这个版本包含了什么" ✓;见 5.5 节。五人小团队做 Web 项目用 Git Flow,维护成本大于收益 ✗
判断:你们的发布节奏需要"版本分支"吗?不需要就别用 ✓
见 5.2、6.2 节。这是 Git Flow 最常见的事故 ✗
解法:PR 模板加检查项 + CI 自动开回合同步的 PR ✓
配了"必须 PR",但没配"禁止 force push" ✗后果:有人仍能通过强推改写历史 ✗
建议:四条基本规则一起配(5.7)✓
见 5.6 节。用工具解决风格,把人的时间留给逻辑与安全 ✓
一个 PR 改了 30 个文件、2000 行 ✗后果:reviewer 看不完(或只看前几行就批准) ⚠️ ——Review 形同虚设 ✗
建议:PR 控制在"能在 20 分钟内看完"的规模(经验值:几百行以内)✓ 大改动拆成多个小 PR(每个都能独立合入)✓
建议:明确"谁负责合并"(通常是作者自己,在 approve 之后)——避免"以为对方会合" ✗
目标:把"分支模型 + 回合 + 保护"这三件事亲手做一遍。
步骤:
--no-ff 合并,观察多分支图;git log --graph --all 确认两个分支都有这个修复 ✓;--no-ff 与快进合并:同一个场景做两次(一次加 --no-ff),对比 git log --graph;git log --oneline --decorate | Select-Object -First 20 —— 看看"每个阶段打一个 tag"的效果 ✓。验收方式:能说出第 3 步"为什么下个版本会复发"、第 4 步两种合并的差别。
扩展任务:为一个"假想团队"写一份 CONTRIBUTING.md:
CONTRIBUTING.md 在根目录,PR 时 GitHub 会自动提示)基础题
代码阅读题
仓库里有这些分支:
main
develop
feature/order-list
feature/payment
release/2.1
hotfix/login-errorhotfix/login-error 修完之后应该合回哪些分支?如果只合回 main 会怎样?编程题
综合任务
提示 1(方向):第 1 题的方向是"没有最好的,只有最合适的"。
提示 2(关键点):第 5 题——hotfix 从哪个分支切的?它修的是哪个环境的问题?
提示 3(接近答案):第 7 题的"验证指标"——想想"PR 从创建到合并的时间""合并时的冲突数量""发布后回滚次数"这类可度量的东西。
判断依据(5.1、5.5):
要同时维护多个版本吗? ──是──→ Git Flow
│否
↓
发布频率?──一天多次──→ Trunk-Based(需要强 CI + Feature Flag)
│
└──随时 / 两周──→ GitHub Flow(**默认推荐**)"Git Flow 听起来很规范"是一种错觉 ✗ ——它的复杂度是有代价的,只在"需要版本分支"时才值得付 ✓
一个现实观察:很多声称"我们用 Git Flow"的团队,实际上只在用它的"main + feature"部分 ✓ ——develop 和 release 分支反而是负担 ⚠️
这说明:模型要跟着实际需要裁剪,而不是照搬 ✓
feature/*、release/*、hotfix/*;hotfix/*。为什么 hotfix 要合回两个分支:
| 分支 | 为什么需要这个修复 |
|---|---|
main | 线上正在出问题——必须立刻修 ✓ |
develop | 下个版本也基于旧代码——不回合就会出现"修过又复发" ✗✗ |
这就是第 4 节"周三那件事"的机制 ✓
对比:
| 口头约定 | 平台规则 | |
|---|---|---|
| 忙起来时 | 被跳过 ✗ | 仍然生效 ✓ |
| 新人不知道 | 照样违反 ✗ | 自动生效 ✓ |
| 出错后 | "我以为可以" | 根本没有这个选项 ✓ |
一个具体的场景:
凌晨两点线上出事,你想"就这一次"直接推 main
· 口头约定:你推了,第二天被发现,大家讨论要不要批评
· 平台规则:push 被拒绝 → 你只能走 PR(但可以走"紧急 PR"流程,仍然留痕)✓关键:平台规则不是"不信任人",而是"让人在紧急时也不会做错" ✓
好的工程实践是"让正确的做法成为最容易的做法" ✓
这与 《参数校验与全局异常处理》 的"统一异常处理"、《Git 分支、合并与回退》 的"--force-with-lease"、《JSP 与 JSTL(技术演进理解)》 的"默认转义"是同一条原则 ✓
第 4 题:这是 Git Flow ✓
判断依据(三条):
| 依据 | 说明 |
|---|---|
有 develop | Git Flow 的标志(GitHub Flow 没有)✓ |
有 release/2.1 | 发布准备分支——Git Flow 特有 ✓ |
有 hotfix/* | Git Flow 特有(GitHub Flow 的紧急修复也是从 main 切的普通分支)✓ |
第 5 题:应该合回 main 和 develop ✓
如果只合回 main:
| 后果 | 说明 |
|---|---|
| 下个版本(release/2.2 或 develop 上的内容)仍然有这个 bug ✗ | 因为 develop 从来没收到这个修复 |
| 而且很难发现 | 测试环境的 develop 分支上,"登录报错"这个问题"确实是存在的"——没人会以为"它应该已经被修了" ⚠️ |
| 直到 2.2 发布后才被发现 ✗✗ | 这时又要再修一次,甚至可能又要一个 hotfix |
这个"很难发现"是问题最麻烦的地方:
如果修了没生效,会立刻被发现;但"忘了同步到另一个分支",要等到下个版本发布才会暴露 ✗
这类"延迟暴露"的问题,正是需要流程/工具来兜住的原因 ✓
三种解法(《Git 分支、合并与回退》 综合任务的延伸):
| 方案 | 强度 | 成本 |
|---|---|---|
| PR 模板加勾选项 | 中 ⚠️ | 低 ✓ |
| CI 检查:hotfix 合并到 main 后,检查 develop 是否包含该提交,不包含就失败 ★ | 强 ✓ | 中 |
| CI 自动开"回合 develop"的 PR ★★ | 最强 ✓ | 中 |
第三个最优:它把"记得做"变成了"确认一下" ✓
实现思路(伪代码):
# CI 在 hotfix 合并到 main 后运行
if git merge-base --is-ancestor <hotfix-commit> origin/develop; then
echo "✓ develop 已包含该修复"
else
echo "✗ develop 缺少这个修复,自动创建回合 PR"
gh pr create --base develop --head main --title "chore: 同步 hotfix 回 develop"
figit merge-base --is-ancestor 是一个很有用的检查:判断"某个提交是否已经在另一个分支上" ✓
选择:GitHub Flow ✓(而不是 Git Flow)
理由(对应场景:"Web 项目、两周一个版本、偶尔紧急修复"):
| 条件 | 对模型的要求 | GitHub Flow 是否满足 |
|---|---|---|
| Web 项目 | 不需要维护多个已安装的版本 ✓ | ✅ |
| 两周一个版本 | 不是"随时发"也不是"多版本并行" ⚠️ | ✅(每周/两周发完全没问题) |
| 偶尔紧急修复 | 需要"从 main 快速切分支修完就上" ✓ | ✅(从 main 切一个 fix 分支即可) |
| 6 人 | 中等规模,需要清晰的规则 | ✅(规则简单反而更容易遵守)✓ |
为什么不用 Git Flow:
"两周一个版本"听起来像"需要 release 分支",但其实不需要 ✗
release 分支的价值在于"冻结功能、只修 bug"——但两周的迭代里,通常直接"从 main 切一个发布标签"就够了,不需要额外一条分支来维护 ⚠️
而且 Git Flow 的
feature → develop → release → main三跳路径,在 6 人团队里会显著增加冲突与同步成本 ✗
⚠️ "偶尔需要紧急修复"为什么影响选择:
| 考虑 | 说明 |
|---|---|
| Git Flow 有专门的 hotfix 分支 ✓ | 看起来"更适合" |
| 但 GitHub Flow 也能做 ✓ | 从 main 切一个 fix/xxx 分支 → PR → 合并 → 部署 ——路径更短 ✓ |
| Git Flow 的 hotfix 必须合回 develop ⚠️ | 这是一条"必须记得"的规则(《Git 分支、合并与回退》 事故 ②) |
| GitHub Flow 没有"开发分支" ★ | 所以根本不存在"忘了同步"这个问题 ✓✓ |
这是本题最关键的一点:
GitHub Flow 因为"没有 develop 分支",从结构上消除了"hotfix 忘了回合"这类事故 ✓
少一个分支,就少一类需要"记得做"的事 ✓
这比"配一个提醒"更彻底 ✓
《分支与协作规范》
分支命名:
main 主干(永远可发布)
feature/<简短描述> 新功能 例:feature/order-export
fix/<简短描述> 缺陷修复 例:fix/login-error-message
hotfix/<简短描述> 线上紧急修复 例:hotfix/payment-timeout
chore/<简短描述> 杂项(依赖升级、文档)例:chore/bump-spring-boot规则:全小写、用短横线、不含中文与空格(避免 URL 与脚本处理出问题)✓
完整流程(从需求到上线):
| 步 | 谁 | 做什么 |
|---|---|---|
| 1 | 开发 | 从 main 切 feature/xxx(切之前先 git pull)✓ |
| 2 | 开发 | 开发 + 自测;提交信息用 Conventional Commits ✓ |
| 3 | 开发 | 提交前:git fetch + git rebase origin/main(同步主干,减少冲突)✓ |
| 4 | 开发 | 开 PR,填模板,关联 issue ✓ |
| 5 | CI | 自动跑测试 + 静态检查(必须全绿)✓ |
| 6 | Reviewer(≥1 人) | 看正确性、安全性、设计;批准 ✓ |
| 7 | 开发 | 合并(Squash and merge,保持 main 历史整洁)✓ |
| 8 | CI/CD | 合并后自动部署到测试环境 → 通过后发布生产 ✓ |
| 9 | 值班 | 线上出事 → 从 main 切 hotfix/xxx → 紧急 PR(1 人 approve 即可) → 合并 → 部署 ✓ |
分支保护规则(四条):
| 规则 | 值 |
|---|---|
| 禁止直接 push 到 main | ✅ 开启 |
| 禁止 force push | ✅ 开启 |
| 必须 PR + 至少 1 人 approve | ✅(紧急修复可放宽为"值班同学 1 人") |
| CI 必须通过 | ✅ 开启 |
推荐再加:"要求分支与 main 同步后才能合并" ✓(避免"合并后 CI 才发现问题")
PR 模板(.github/pull_request_template.md):
## 做了什么
(一句话说明)
## 为什么
(关联 issue:Closes #123)
## 怎么验证的
- [ ] 正常流程
- [ ] 边界情况(列出哪些)
- [ ] 错误情况
## 需要重点 review 的地方
(★ 例如:"改了金额计算,请重点看对账逻辑与取整")
## 风险与回滚方式
(有问题时怎么回滚:关开关 / 回退版本)⚠️ "风险与回滚方式"这一项值得单独强调:
写 PR 时想一遍"如果上线后出问题怎么办" ——这会倒逼你在实现时就考虑可回滚性 ✓
比如:新逻辑用 Feature Flag 包起来(这样回滚 = 关开关,不用重新发版)✓
逐条根因分析
| # | 现象 | 根因(不是"要记得") |
|---|---|---|
| ① | feature 分支平均存活 3 周,冲突多 | 不是"合并技巧不够",而是"分支存活太久" ✗ ——3 周内主干已经前进了很多(《Git 分支、合并与回退》 5.3 的"合并痛苦与存活时间超线性关系") |
| ② | 回合 develop 总有人忘 | 不是"记性不好",而是"这件事依赖人记得,且做错了不会立刻报错" ✗ ——延迟暴露的问题必须靠工具 ✓ |
| ③ | PR 平均等 2 天 | 不是"大家不积极",而是"没有明确 review 的优先级" ⚠️ ——review 被排在"自己的开发"后面 |
| ④ | 直接 push develop | 不是"不守规矩",而是"这件事根本没有被禁止" ✗ ——没有平台规则 = 默认允许 |
第 ④ 条最关键:
"没配保护"本身就是一种设计——它的意思是"允许任何人直接推"。
问题不在违规的人,在于规则不存在 ✓
改进措施(技术优先)
| # | 措施 | 类型 |
|---|---|---|
| ① | 缩短分支寿命:大功能拆小;每天同步主干(git rebase origin/develop)✓ | 流程 + 习惯 |
| ① | 引入 Feature Flag:长期功能在主干上开发,用开关关掉 ✓ | 技术 ★★ |
| ② | CI 自动开"回合同步"的 PR ✓ | 技术 ★★ |
| ② | 或 CI 检查 develop 是否包含该修复,不包含就失败 ✓ | 技术 ★ |
| ③ | 规定 review 的 SLA(如"PR 创建后 24 小时内必须有人看")+ 每天固定一个"review 时段" ✓ | 流程 |
| ③ | PR 拆小(几百行以内)→ review 更快 ✓ | 流程 |
| ④ | 配分支保护:禁止直接 push、禁止强推、必须 PR ✓ | 技术 ★★★ |
验证改进是否有效的指标:
| 指标 | 怎么测 | 目标 |
|---|---|---|
| 分支存活时间 | 从分支创建到合并的天数(可用 CI 或脚本统计) | 从 3 周 → < 3 天 ✓ |
| 合并时的冲突数 | 合并时冲突文件数(或"需要手工解决的冲突次数") | 显著下降 ✓ |
| PR 首次 review 时间 | 创建到第一个评论/approve | 从 2 天 → < 4 小时 ✓ |
| PR 从创建到合并 | — | 下降 ✓ |
| 直接 push 到 develop 的次数 | 平台审计日志 | 0 ✓✓ |
| 发布后回滚次数 | 每次发布记录 | 下降 ✓ |
| "修过又复发"的 bug 数 | issue 里标记为"重复出现"的 | 0 ✓✓ |
怎么获取这些指标:平台的 API(GitHub/GitLab 都能查 PR 与合并数据)+ 一个简单的统计脚本 ✓
只做一件,先做哪件:④ 配分支保护 ✓
理由:
| 理由 | 说明 |
|---|---|
| 成本最低 | 在平台上勾几个复选框,10 分钟 ✓ |
| 效果最确定 | "禁止"是二元的——配了就 100% 生效 ✓ |
| 风险最高 | 直接 push develop 可能绕过所有 review 与 CI ✗✗ ——这是"最危险的口子" ✓ |
| 不依赖他人配合 | 其他三条都需要改变团队习惯(需要时间);这条不需要 ✓ |
对照 《Filter、Listener 与会话管理》 的"默认拒绝 + 白名单":
| 《Filter、Listener 与会话管理》 的鉴权 | 本题的分支保护 | |
|---|---|---|
| 默认 | 拒绝(全拦)✓ | 拒绝(禁止直接 push)✓ |
| 显式放行 | 白名单(/login、/health)✓ | PR + approve(显式的审核动作)✓ |
| 漏配的后果 | 多拦了 → 立刻发现 ✓ | 多拦了 → 立刻发现 ✓ |
| 反过来的做法 | 枚举"需要保护的路径" ✗ 迟早漏 | "允许直接 push,请大家自觉" ✗ 迟早出事 |
| 核心原则 | Deny by Default ✓ | Deny by Default ✓ |
共同原则:
"默认允许 + 希望大家守规矩"是不可靠的;"默认拒绝 + 显式放行"才是可依赖的。 ✓
这条原则贯穿本知识库的很多地方:
- 《Filter、Listener 与会话管理》:鉴权用"全拦 + 白名单"
- 《参数校验与全局异常处理》:异常用"兜底 handler"
- 《Spring Boot 配置管理与多环境》:配置的"安全默认值"
- 《Git 分支、合并与回退》:
--force-with-lease而非--force- 《JSP 与 JSTL(技术演进理解)》:模板的"默认转义"
它们的共同点是:把"正确性"从"人的注意力"转移到"机制"上 ✓
因为人的注意力是稀缺且不可靠的资源,而机制不是。 ✓
《Maven 进阶:依赖管理与多模块》:把"构建与依赖"这一层管起来。
暂无