📦 第02章 · 分支/合并/变基
branch checkout merge rebase
分支、合并与变基¶
上一章我们学会了把改动一次次提交。但所有提交都堆在 main 上,出了问题很难收拾。分支 (branch) 就是 Git 给你的一条"平行轨道":可以大胆地另起一条线开发新功能,互不干扰,最后再把成果并回来。这一章讲清 branch、merge、rebase 三件事。
分支的本质:一个会移动的指针¶
Git 的提交对象(chapter 1 的 git log 里那串短哈希)像单向链表,每个提交记录它的父提交。分支只是一根指向某个提交的"可移动指针"(label)。当你 commit,当前分支的指针就自动向前滑一格,指向新提交。
main
│
A ← B ← C ← D (main 指向 D,HEAD 也指向 main)
main feature
│ │
A ← B ← C ← D ← E ← F (在 feature 上又提交了 E、F)
因为创建分支只是加一个指针,而不是复制文件,所以 Git 建分支极快、极省——这也是它广受欢迎的原因。
| 术语 | 含义 |
|---|---|
HEAD |
指向"你当前所在分支"的指针,Git 用它在哪根轨道上移动 |
main |
默认主干分支(传统叫法 master,第 1 章已用 init.defaultBranch 设为 main) |
| 检出 (checkout/switch) | 把工作区切换成某分支指向的版本,同时移动 HEAD |
创建与切换分支¶
现代 Git 推荐用 switch 替代老式的 checkout,语义更清晰:
git branch # 列出所有本地分支,★ 标记当前分支
git branch feature/login # 从当前分支创建新分支 feature/login(不切换)
git switch feature/login # 切换过去
git switch -c feature/login # 创建并立刻切换(一步完成)
git switch - # 切回上一个分支(类似 cd -)
git switch main && git branch -d feature/login # 删已合并分支
git branch -D feature/login # 强制删除未合并分支(危险)
安全
git branch -D(大写)会直接丢弃分支上尚未合并的提交。只有当确定这些提交不要了,才用大写-D;默认请先用小写-d,Git 会拒绝删除未合并的分支作为保护。
实战习惯:开发新功能前,先从最新的 main 切一条分支,把它完全隔离。例如给登录页做改造:
git switch main # 确保基于最新 main
git switch -c feature/login
# ...写代码、git add、git commit(可以在 feature/login 上提交多次)...
git switch main # 回到主干
git log --oneline # 主干仍是干净的历史,没有功能分支的提交
git merge:把分支合回来¶
做完功能,要把 feature/login 并入 main,用 merge。合并分两种:
| 类型 | 触发条件 | 结果 | 何时偏好 |
|---|---|---|---|
| Fast-forward (快进) | main 在分支分叉后没有任何新提交 |
main 指针直接滑到 feature/login 的尖端,线性,无额外提交 |
简单、想保持线性时 |
| 三方合并 (merge commit) | main 在分叉后也有新提交 |
生成一个新的合并提交(merge commit),有两个父提交 | 想保留"在哪合过"的痕迹时 |
git switch main
git merge feature/login # 自动尝试合并,若无冲突会产出新提交
git merge --no-ff feature/login # 强制生成合并提交,即使可以快进
git merge --squash feature/login # 把整条分支压缩成一次提交(只合并改动,不留分支痕迹)
看 git log --oneline --graph 就能辨认:合并会产生分叉又汇合的形状(像个"门字形")。
合并冲突¶
当两个分支改了同一个文件的同一行,Git 无法自动判断谁对,就停下来让你裁决,这就是冲突 (conflict)。合并后 Git 会在文件里插入冲突标记:
<<<<<<< HEAD
console.log("来自 main 的版本");
=======
console.log("来自 feature/login 的版本");
>>>>>>> feature/login
处理步骤:
- 打开冲突文件,在
<<<<<<<与>>>>>>>之间手工保留想要的那一行,删掉标记行; - 保存文件并
git add 该文件; - 再次
git commit(不带-m,Git 会自动填充"合并"信息,建议核对)。
git merge feature/login # 报冲突:CONFLICT (content)
git status # 显示 both modified,待解决
# 编辑文件合并标记...之后
git add app.js
git commit # 完成合并
易错 合并冲突不是错误,不要慌着执行
git merge --abort逃课。逐文件解决才是正途;如果确实想完全放弃这次合并、回到合并前状态,再用git merge --abort。
git rebase:把分支"挪"到最新主干上¶
rebase(变基)是另一种整合思路:它把你 feature 分支上的提交,一条条"重放"到目标分支的最新提交之后,让历史保持一条直线,没有合并提交。
合并前:
main
│
A ← B ← C ← E (feature: C ← D 分叉自 B)
│
D (feature)
rebase main 后:
A ← B ← C ← D ← E 单条直线,D、E 都在 main 上
git switch feature/login
git rebase main # 把当前分支的提交重放到 main 之后
git rebase --continue # 解决冲突后继续
git rebase --abort # 放弃本次 rebase,回到之前状态
merge 与 rebase 怎么选¶
| 维度 | merge | rebase |
|---|---|---|
| 历史形状 | 有分叉与合并提交,保留"在何处合并" | 线性直线,更干净易读 |
| 是否改写提交 | 不改写已有提交 | 改写了被重放提交的哈希 |
| 对已推送分支的影响 | 安全 | 会改动历史,已共享的分支别人拉取会冲突 |
| 适用场景 | 把功能分支合进主干、长期分支 | 拉取到新主代码后,把个人分支更新到最新,再看 diff |
一条通用经验:
- 本地的、尚未推送的个人分支 → 用 rebase 保持线性、好读;
- 已经推送到共享远程、或多人协作的分支 → 用 merge,不要 rebase。
安全 对已经推送到共享仓库的分支执行
rebase并force push,会改写别人的历史,是很危险的操作。除非你是这条分支的唯一作者且确认无人依赖,否则一律merge,并绝不git push --force。这可能是 Git 团队协作里最容易出事故的动作。
案例场景:一次完整的功能分支开发¶
用本服务器上一个前端项目举例,从 main 拉分支、开发、重基、合并:
git switch main
git switch -c feature/dashboard
# 开发并提交若干次
echo "export default (/* */) => 1;" >> dashboard.js
git add dashboard.js
git commit -m "feat: 仪表盘骨架"
# 期间主线上也有别人提交,先拉最新再变基保持线性
git switch main
git pull --rebase # 见第 3 章:拉取最新并变基,而不是合并
git switch feature/dashboard
git rebase main
# 若无冲突,回到主干合并
git switch main
git merge feature/dashboard # 此时一般是快进,历史为一条直线
git log --oneline --graph
若合并产生冲突,按上面冲突流程手动解决;git rebase --continue 后历史依旧呈单线。
延伸:更常用的分支工具¶
git stash:临时把工作区未提交的改动"存起来",切去别的分支不带着它,git stash pop随时找回。适合改了一半想临时切走。git cherry-pick <哈希>:把别处某一次提交的改动复制到当前分支,不合并整条分支。git switch --detach/ 直接切哈希:让HEAD指向某个不在分支上的提交,即"游离 HEAD"。可以查看旧版本,但这种状态下commit会留在游历处,很容易弄丢,看完记得切回分支。git worktree:同一仓库同时检出多个分支到不同目录,适合一台服务器上并行改多个分支(第 3 章部署时很实用)。
易错点¶
- 在错误分支上提交:忘了
switch,把改动提交到了main或别人分支。养成开工前git status看当前分支、git switch -c一步到位。 --no-ffvs--squash混淆:--no-ff保留合并痕迹与所有提交;--squash只把改动压成一次提交、不留分支合并历史,二者方向相反。- 解决冲突时没删干净标记:忘了删除
<<<<<<<、=======、>>>>>>>三行,提交进去会在源码里留着怪异文本。解决后用grep "<<<<<<<" 文件复查。 - 合并与变基混用当万能药:变基改写提交,不适合共享分支;合并产生额外提交,不适合想保持干净主线。按"是否已推送"来选。
相关命令¶
git branch、git switch、git checkout、git merge、git rebase、git stash、git cherry-pick、git reset、git show、git log --graph。