开发者几乎每天都会用 Git,但遇到合并分支时,'Merge 还是 Rebase?'这个问题让很多人头疼。无脑用 Merge,提交历史会变成一团乱麻;盲目追 Rebase,搞砸公共分支的事故我也见过不少。
想把这两个命令用对,得先知道 Git 分支的底子是什么。
Git 分支的本质
Git 的快照模型很简单:每一次提交(commit)都是一个不可变对象,里面有当时的文件树(tree)、一个或多个 parent 指针、作者和提交者信息,全加起来算出一个唯一的 SHA-1。只要任何一个信息变了,hash 就不同,这就是一个全新提交。
分支不过是一个指向某个提交的轻量指针,换分支只改 HEAD 指针,再把工作区切过去。理解了这一点,后面的合并操作才好懂。
Merge:老实人做法,完整保留一切
Merge 的名字已经说明了一切——把两个开发历史合并起来,不动现有的提交,只生成一个新的合并提交。根据两个分支有没有分叉,实际会走两条路。
快进合并(Fast-Forward)
如果特性分支开发时,目标分支完全没动,那就没有分叉。Git 直接把目标分支指针挪到特性分支最新提交,完事。提交历史是一条直线,什么多余痕迹也没有。
# 初始化仓库
mkdir merge-rebase-demo && cd merge-rebase-demo
git init
git config user.name "Demo User"
git config user.email "[email protected]"
echo "# Merge vs Rebase Demo" > README.md
git add README.md
git commit -m "chore: init project"
# 切出特性分支并提交
git checkout -b feature/docs
echo "## Quick Start" >> README.md
git add README.md
git commit -m "docs: add quick start guide"
# 回到 main,它没动,直接合并
git checkout main
git merge feature/docs
结果:git log --oneline --graph 看到的就是一条直线,main 指针直接移到 45f8d21,没有任何新 commit。
三方合并(Three-Way Merge)
多数时候目标分支不会闲着。一旦分叉,Git 就做一次三方合并:找出两个分支的最新提交和它们的共同祖先(LCA),计算差异并自动合并,最后生成一个有两个 parent 的合并提交。这个合并提交完好记录了分合过程。
复现一下:
# 重置 main 到第一次提交,删掉 feature/docs
git reset --hard a7c3d90
git branch -D feature/docs
# 重开 feature/login 并提交两次
git checkout -b feature/login
mkdir src
echo "// user login core logic" > src/login.js
git add src/login.js
git commit -m "feat: add login core logic"
echo "// login input validation" >> src/login.js
git add src/login.js
git commit -m "feat: add login validation"
# main 也提交一次,故意分叉
git checkout main
echo "// project base config" > config.js
git add config.js
git commit -m "chore: add base config"
# 合并
git merge feature/login
合并后查看历史:
* 3f7e2d1 (HEAD -> main) Merge branch 'feature/login'
|\
| * c8d3f4 (feature/login) feat: login validation
| * d9e4f5 feat: login core logic
* | a6b7c8 chore: config
|/
* a7c3d90 chore: project


