前言
为什么团队开发项目总在强调分支隔离?
别搞那么复杂,直接在
develop上改吧,改完同步一下就行。
-
“就改一个小功能,没必要专门切分支吧?”
-
“只是修个样式,走完整套流程也太重了。”
-
“平时大家不也都是这么干的,好像也没出过什么事。”
这些话在大多数时候都是“对的” 直到某一次事故,将会教会你分支隔离的意义
分支隔离做了什么
在此之前笔者也不觉得分支隔离是必须的,只是习惯性作为一个习惯,除了感觉能带来合并时候的 review 方便没有感受其他实质性帮助,直到今天一个三人协同的远端分支出现了分叉;
直到这次事故发生:
只是点了一次 Sync,远端 develop 为什么会突然分叉?
猛然意识到:
分支隔离并不是为了规范流程,而是为了避免某些“以为只是同步,其实是在做 merge的时刻。
场景复原
下文统一用 A、B、C 代指三明成员,梳理输出对应关键信息,特有信息已做混淆处理
先看当时发生了什么。
在一个不到 1 小时的时间窗口里:
- B 把一个 release 分支合进了主线
- A 在本地继续开发,并提交了一次代码
- C 又在远端主线上推进了一次修改
问题在于:
A 的本地分支,其实已经不是最新的。但他自己并没有意识到这一点。
于是他做了一个非常“日常”的操作:
👉 点了一下 VS Code 的 Sync
也就是这一刻,事情开始失控
关键提交如下:
| 时间 | 提交 | 角色 | 含义 |
|---|---|---|---|
| 10:49:22 +08 | COMMIT_B1 | B | 将 branch_release_A 合入 branch_mainline |
| 11:11:55 +08 | COMMIT_A1 | A | A 在本地 branch_mainline 上继续开发并提交 |
| 11:17:29 +08 | COMMIT_C1 | C | C 在远端 branch_mainline 线上继续推进字体修改 |
| 11:20:05 +08 | MERGE_A2 | A | A 解决冲突后生成的 merge commit,但结果树已异常 |
| 11:52:48 +08 | MERGE_A3 | A | 再次 Sync 后,把错误树和 C 的提交一起写到了远端 |
COMMIT_A1、COMMIT_B1、COMMIT_C1并不是按一条线串行发生的,而是多人在共享branch_mainline上并行推进的。
远端 mainline:
COMMIT_B1 ── COMMIT_C1
\
(A本地分支)
COMMIT_A1
\
MERGE_A2(错误)
\
MERGE_A3 → push(污染远端)
1️⃣ A 在“过期主线”上开发
- 本地
branch_mainline落后于远端 - 但仍直接在上面提交(
COMMIT_A1)
2️⃣ 同一时间多人直接操作主线
- B、C、A 都在直接改
branch_mainline - 没有隔离分支(feature 分支)
👉 导致:历史出现分叉(diverge)
为什么出现了分叉
这次问题表面上看,是 VS Code 的同步按钮把远端 branch_mainline 弄分叉了。
我一直都是用这个功能提交的,为什么今天出了问题
问题的关键不在 push,而是在第一次 Sync 时,Git 已经进入了 merge。
换句话说:
A 以为自己是在“同步代码”,
但实际上,Git 在本地帮他做了一次合并。
git pull --tags origin branch_mainline
# 实际含义:fetch + merge
git push origin branch_mainline:branch_mainline
如果用流程表示,大概是这样:
flowchart TD
A[共同旧基点 BASE_A]
A --> B[A 本地提交 COMMIT_A1]
A --> C[远端前进到 COMMIT_B1]
B --> D[第一次 Sync<br/>git pull 触发 merge]
C --> D
D --> E[本地生成 MERGE_A2<br/>分支开始偏离]
C --> F[远端继续前进到 COMMIT_C1]
E --> G[第二次 Sync]
F --> G
G --> H[MERGE_A3 被 push 到远端]
H --> I[远端 branch_mainline 出现并行线<br/>且尖端携带错误树]
- 第一次 Sync 没有把 A 直接更新到远端最新,而是把 A 的本地提交和远端状态做了 merge。
- 第二次 Sync 又把这条已经偏离的本地线继续推回远端,于是远端也开始偏移。
由于:
git pull → git merge
结果:
- 产生
MERGE_A2 - 冲突被错误解决
- 形成“错误的代码树”
不是工具问题,是协同策略隐患
这时候一个很自然的问题就会出现:
“我一直都是这么点 Sync 的,为什么这次出问题?”
一直以来笔者也认为 Sync 按钮只是执行简单的 pull 与 push 操作,但查看 Git 执行日志出现了 merge ???
真正决定行为的,是当前分支的状态。
当出现下面条件时:
- 本地有提交
- 远端也有新提交
- pull.rebase=false
那一次普通的 pull,就一定会变成:
👉 fetch + merge
Git 的同步语义执行是依赖于 pull.rebase 配置的,这造成了
- 如果本地只是落后远端,可能是快进更新。
- 如果本地和远端都前进了,而且
pull.rebase=false,那pull就一定会触发 merge。
VS Code 并没有做错什么,它一直都是这么工作的。
长歪的结果树
merge 不是“选最近的那棵树”,而是基于共同祖先做三方合并,把当前暂存区里的结果写成新树
这就是真正危险的地方 👉 merge 的结果,不一定都有问题,但麻烦的是 它看起来是对的 。
很多人会以为:
- 冲突解决了 → 就安全了
- 能提交 → 就没问题
但 Git 并不会帮你保证结果树是对的。它只是把你当前暂存区的内容,写成一个新的 commit。
所以一旦冲突处理不完整,或者误操作:
在不知不觉中,远端内容将被覆盖
sequenceDiagram
participant B as B
participant A as A 本地 branch_mainline
participant V as VS Code Sync
participant R as 远端 branch_mainline
participant C as C
B->>R: 合入 COMMIT_B1
A->>A: 基于旧基点提交 COMMIT_A1
A->>V: 点击 Sync #1
V->>R: pull branch_mainline
R-->>V: 返回包含 COMMIT_B1 的最新状态
V->>A: 在本地做 merge
A->>A: 形成 MERGE_A2
Note over A: 本地分支开始偏离
C->>R: 追加 COMMIT_C1
A->>V: 点击 Sync #2
V->>R: 再次 pull 后 push
V->>A: 本地合入 COMMIT_C1
V->>R: 推送 MERGE_A3
R->>R: 远端出现并行线
本地 `branch_mainline` 跟踪远端 `branch_mainline` 的 pull / push 关系;
`pull.rebase=false`;
`11:12:45` 的 `git pull --tags origin branch_mainline`;
`11:15:22` 的未解决冲突报错;
`11:15:36` 的单文件 `git add`;
`11:20:05` 的 `MERGE_A2`;
`11:52:48` 的再次 `pull`;
`11:52:50` 的 `push origin branch_mainline:branch_mainline`;
`git show -m --stat MERGE_A2` 和 `git show -m --stat MERGE_A3` 的父节点差异。
终结
这不是 VS Code 的 Bug,而是“共享主线被当成日常开发分支”之后,被 GUI 按钮放大的协作事故。
本质问题在于:
👉 共享主线,被当成了日常开发分支
很多团队不是不会用 Git,习惯用一个“看起来很轻”的操作,去触发一件“实际上很重”的行为。
没出问题的时候,大家会觉得“这样改更快”。
tip:如果本机还需要保留 branch_mainline 用于查看和同步,建议至少配置:
git config pull.ff only
这样一旦本地和远端分叉,Git 会直接失败,而不是自动 merge。
共享主线,不应该被当成日常开发分支
问题从来不是 Sync 按钮,而是我们在用错误的方式使用主线分支。
分支隔离的意义,也不是“规范流程”,而是: 避免在错误的时间、错误的分支上发生 merge
每条规则背后,必然有案例曾经发生