Merge 与 Rebase 实战演示 概述 本笔记通过模拟两个开发者协作 的方式,实际演示 git merge 和 git rebase 的区别,重点展示 rebase 在公共分支上使用时为什么会出问题。
演示思路 用两个本地文件夹(me/ 和 xiaowang/)分别 clone 同一个远程仓库,模拟两个人在不同电脑上操作。
演示环境
远程仓库 :https://gitee.com/Zmmmmy/notes.git
本地根目录 :E:\ZhiMy\Code\git
“我”的仓库 :E:\ZhiMy\Code\git\me
“小王”的仓库 :E:\ZhiMy\Code\git\xiaowang
一、环境准备 1.1 创建演示目录 1 2 mkdir E:\ZhiMy\Code\gitcd E:\ZhiMy\Code\git
1.2 Clone 两份仓库(模拟两个人) 1 2 3 4 5 6 7 git clone https://gitee.com/Zmmmmy/notes.git me git clone https://gitee.com/Zmmmmy/notes.git xiaowang
💡 提示: 为什么 clone 两份? 现实中你和小王是两台不同的电脑,各自 clone 了一份。这里用两个文件夹来模拟。
1.3 创建 dev 分支并推送到远程 1 2 3 4 5 6 7 8 9 10 cd E:\ZhiMy\Code\git\me git checkout -b dev git push -u origin dev
1 2 3 4 5 cd E:\ZhiMy\Code\git\xiaowang git fetch origin git checkout dev
1.4 确认初始状态
此时三边状态一致:
1 2 3 远程 dev: A 我本地 dev: A 小王本地 dev: A
📝 注意: A 代表什么? A 代表当前 dev 分支上最新的那个 commit,三边都指向同一个 commit。
二、安全场景:rebase 未 push 的提交
✅ 成功: 结论先行 如果你的提交还没有 push 到远程 ,rebase 是安全的,不会影响任何人。
2.1 小王先提交并 push 1 2 3 4 5 6 7 cd E:\ZhiMy\Code\git\xiaowangecho "小王写的功能代码" > feature.txt git add feature.txt git commit -m "B: 小王添加功能" git push origin dev
实际输出:
1 2 3 4 [dev 0324220] B : 小王添加功能 1 file changed, 1 insertion (+) create mode 100644 feature.txt9 dac2d9..0324220 dev -> dev
此时状态:
1 2 3 远程 dev: A --- B 我本地 dev: A ← 我还不知道有 B 小王本地 dev: A --- B
2.2 我在本地提交(没有 push) 1 2 3 4 5 6 cd E:\ZhiMy\Code\git\meecho "我写的登录代码" > login.txt git add login.txt git commit -m "C: 我添加登录功能"
实际输出:
1 2 3 [dev 2a4a8ad] C: 我添加登录功能 1 file changed, 1 insertion (+) create mode 100644 login.txt
此时状态:
1 2 3 远程 dev: A 我本地 dev: A 小王本地 dev: A
2.3 我用 rebase 同步远程最新代码 1 2 3 4 5 cd E:\ZhiMy\Code\git\me git fetch origin dev git rebase origin/dev
实际输出:
1 2 3 4 5 6 7 From https://gitee.com/Zmmmmy/notes * branch dev -> FETCH_HEAD 9dac2d9..0324220 dev -> origin/dev Successfully rebased and updated refs/heads/dev.
rebase 把我的 C “搬”到了 B 后面,变成 C’:
1 2 3 远程 dev: A (9d ac2d9) --- B(0324220 ) 我本地 dev: A (9d ac2d9) --- B(0324220 ) --- C'(86d 4af7) ← C(2 a4a8ad) 被复制到 B 后面,hash 变了 小王本地 dev: A (9d ac2d9) --- B(0324220 )
⚠️ 重要: 为什么安全? 因为 C(2a4a8ad) 从来没有 push 过,只有我本地有 C 。C 变成 C’(86d4af7) 不影响任何人。
2.4 我 push,小王 pull,一切正常 1 2 3 4 cd E:\ZhiMy\Code\git\me git push origin dev
实际输出:
1 0324220. .86 d4af7 dev -> dev
1 2 3 4 cd E:\ZhiMy\Code\git\xiaowang git pull origin dev
实际输出:
1 2 3 4 5 6 7 8 From https: * branch dev -> FETCH_HEAD 0324220 ..86 d4af7 dev -> origin/dev Updating 0324220 ..86 d4af7 Fast-forward login.txt | 1 + 1 file changed, 1 insertion(+) create mode 100644 login.txt
最终三边一致:
1 2 3 --- --- --- --- --- ---
2.5 查看历史:一条干净的直线 1 git log --oneline --graph
实际输出:
1 2 3 4 * 86 d4af7 (HEAD -> dev, origin/dev) C: 我添加登录功能 * 0324220 B: 小王添加功能 * 9 dac2d9 (origin/master, origin/HEAD, master) no message * b4649f3 Initial commit
✅ 成功: 安全场景总结 我的提交 C(2a4a8ad) 从未 push 过 ,所以 rebase 把它变成 C’(86d4af7) 不影响任何人。历史干净整洁,是一条直线。
三、危险场景:改写已 push 的提交(commit –amend)
🔥 危险: 结论先行 如果你的提交已经 push 到远程 ,并且别人已经拉取了,此时用 commit --amend(或 rebase)改写历史会导致协作混乱。commit --amend 本质上和 rebase 一样,都是改写已有提交的 hash 。
3.0 重置环境 先把三边恢复到同一起点(commit A = 9dac2d9),方便演示:
1 2 3 4 5 6 7 8 9 10 11 cd E:\ZhiMy\Code\git\me git checkout dev git reset --hard 9dac2d9 git push --force origin devcd E:\ZhiMy\Code\git\xiaowang git checkout dev git fetch origin git reset --hard origin/dev
此时三边回到一致状态:
1 2 3 远程 dev : A (9 dac2d9 ) 我本地 dev : A (9 dac2d9 ) 小王本地 dev : A (9 dac2d9 )
3.1 我先提交并 push 1 2 3 4 5 6 7 cd E:\ZhiMy\Code\git\meecho "我写的登录代码" > login.txt git add login.txt git commit -m "B: 我添加登录功能" git push origin dev
实际输出:
1 2 3 4 [dev ee3309e] B: 我添加登录功能 1 file changed, 1 insertion (+) create mode 100644 login.txt 9 dac2d9..ee3309e dev -> dev
此时状态:
1 2 3 远程 dev : A (9 dac2d9 ) --- B (ee3309e ) 我本地 dev : A (9 dac2d9 ) --- B (ee3309e ) 小王本地 dev : A (9 dac2d9 ) ← 小王还不知道有 B
3.2 小王 pull 后也提交并 push 1 2 3 4 5 cd E:\ZhiMy\Code\git\xiaowang git pull origin dev
实际输出:
1 2 3 4 5 6 7 8 From https: * branch dev -> FETCH_HEAD 9 dac2d9 ..ee3309e dev -> origin/dev Updating 9 dac2d9..ee3309e Fast-forward login.txt | 1 + 1 file changed, 1 insertion(+) create mode 100644 login.txt
1 2 3 4 5 echo "小王写的功能代码" > feature.txt git add feature.txt git commit -m "C: 小王添加功能" git push origin dev
实际输出:
1 2 3 4 [dev bc6a152] C: 小王添加功能 1 file changed, 1 insertion (+) create mode 100644 feature.txt ee3309e..bc6a152 dev -> dev
此时状态:
1 2 3 远程 dev : A (9 dac2d9 ) --- B (ee3309e ) --- C (bc6a152 ) 我本地 dev : A (9 dac2d9 ) --- B (ee3309e ) ← 我还没拉 C 小王本地 dev : A (9 dac2d9 ) --- B (ee3309e ) --- C (bc6a152 )
⚠️ 警告: 关键点 此时远程和小王都有 B(ee3309e) 这个提交(hash 相同),这是正常的。
3.3 我用 commit –amend 修改已 push 的提交(作死操作) 我觉得之前的提交 B 写得不够好,想修改一下内容和提交信息:
1 2 3 4 5 6 7 8 9 cd E:\ZhiMy\Code\git\meecho "我写的登录代码(修改版)" > login.txt git add login.txt git commit --amend -m "B: 我添加登录功能(改进版)"
实际输出:
1 2 3 4 [dev 7bf219b] B: 我添加登录功能(改进版) Date: Mon Feb 10 17:51:42 2026 +0800 1 file changed, 1 insertion(+) create mode 100644 login.txt
此时状态:
1 2 3 远程 dev : A (9 dac2d9 ) --- B (ee3309e ) --- C (bc6a152 ) 我本地 dev : A (9 dac2d9 ) --- B '(7bf219b) ← B 被改写成 B' ,hash 变了!小王本地 dev : A (9 dac2d9 ) --- B (ee3309e ) --- C (bc6a152 )
🔥 危险: 问题出现了 commit --amend 并不是”修改”提交,而是用一个新提交替换旧提交 。 B(ee3309e) 和 B’(7bf219b) 内容不同、hash 不同,Git 认为它们是两个完全不同的提交 。 但远程和小王那里还保留着旧的 B(ee3309e)!
3.4 我 push 不上去,强制推送 1 2 3 4 5 cd E:\ZhiMy\Code\git\me git push origin dev
实际输出:
1 2 3 4 5 To https://gitee.com/Zmmmmy/notes.git ! [rejected] dev -> dev (non-fast-forward)error: failed to push some refs to 'https://gitee.com/Zmmmmy/notes.git' hint: Updates were rejected because the tip of your current branch is behind hint: its remote counterpart.
1 2 git push --force origin dev
实际输出:
1 + bc6a152...7 bf219b dev -> dev (forced update )
此时状态:
1 2 3 远程 dev : A (9 dac2d9 ) --- B '(7bf219b) ← 被我强制覆盖!B 和 C 都没了! 我本地 dev: A(9dac2d9) --- B' (7 bf219b ) 小王本地 dev : A (9 dac2d9 ) --- B (ee3309e ) --- C (bc6a152 ) ← 小王还是旧的
🔥 危险: 注意 强制推送后,远程的 B(ee3309e) 和 C(bc6a152) 都被覆盖了。小王的提交 C 在远程直接消失了!
3.5 小王 pull,灾难发生 1 2 3 4 cd E:\ZhiMy\Code\git\xiaowang git pull origin dev
实际输出:
1 2 3 4 5 6 From https:/ / gitee.com/ Zmmmmy/ notes * branch dev - > FETCH_HEAD + bc6a152...7 bf219b dev - > origin/ dev (forced update ) Auto- merging login.txt CONFLICT (add / add ): Merge conflict in login.txt Automatic merge failed; fix conflicts and then commit the result.
打开 login.txt,看到冲突内容:
1 2 3 4 5 <<<<<<< HEAD我写的登录代码 ======= 我写的登录代码(修改版) >>>>>>> 7bf219b46ef1b1fc2f00f335d24d22e6011a472e
🔥 危险: 这就是问题所在 小王明明什么都没改 login.txt,却莫名其妙遇到了冲突! 原因是:我用 --amend 改写了 B 的内容,force push 后远程的 B’(7bf219b) 和小王本地的 B(ee3309e) 内容不同,Git 不知道该用哪个版本。
更严重的是,小王的提交 C(bc6a152) 在远程已经消失了 ,如果小王不小心处理,这个提交可能永远丢失。
四、对比:同样的场景用 merge 回到 3.2 结束时的状态,这次”我”不修改已有提交 ,而是直接用 git pull(merge)来同步:
1 2 3 远程 dev : A (9 dac2d9 ) --- B (ee3309e ) --- C (bc6a152 ) 我本地 dev : A (9 dac2d9 ) --- B (ee3309e ) ← 我还没拉 C 小王本地 dev : A (9 dac2d9 ) --- B (ee3309e ) --- C (bc6a152 )
4.1 我用 merge 同步(正确做法) 1 2 3 4 5 cd E:\ZhiMy\Code\git\me git pull origin dev
实际输出:
1 2 3 4 5 6 7 From https: * branch dev -> FETCH_HEADUpdating ee3309e..bc6a152 Fast-forward feature.txt | 1 + 1 file changed, 1 insertion(+) create mode 100644 feature.txt
Git 自动把 C 合并进来(快进合并),不会改写任何已有提交的 hash :
1 2 3 远程 dev : A (9 dac2d9 ) --- B (ee3309e ) --- C (bc6a152 ) 我本地 dev : A (9 dac2d9 ) --- B (ee3309e ) --- C (bc6a152 ) ← 直接快进合并小王本地 dev : A (9 dac2d9 ) --- B (ee3309e ) --- C (bc6a152 )
4.2 查看历史:干净且无重复 1 git log --oneline --graph
实际输出:
1 2 3 4 * bc6a152 (HEAD -> dev, origin/dev) C: 小王添加功能 * ee3309e B: 我添加登录功能 * 9 dac2d9 (origin/master, origin/HEAD, master) no message * b4649f3 Initial commit
✅ 成功: merge 的优势 所有提交的 hash 都没变,三边完全一致,小王 pull 也不会有任何问题。没有冲突,没有丢失提交。
五、总结 核心区别一句话
操作
效果
风险
merge / pull
保留原有提交,可能多一个合并节点
无风险
rebase / commit --amend
改写提交 hash,历史变成直线
已 push 的提交被改写会影响他人
什么时候用哪个?
场景
推荐
原因
个人分支,提交未 push
rebase
没人有旧 hash,安全
个人分支,已 push 但只有自己用
rebase
可以 --force,不影响他人
多人共用的分支
merge
不改写历史,协作安全
合并功能分支到 main
merge
保留完整开发记录
黄金法则
🔥 危险: 黄金法则 不要对已经推送到远程的公共分支执行 rebase 或 commit –amend。
判断标准很简单:这个分支除了你,还有没有别人在用?
只有你 → 可以 rebase / amend
有别人 → 用 merge
六、清理演示环境 演示完成后,删除本地模拟目录:
1 2 3 rd /s /q E:\ZhiMy\Code\git\me rd /s /q E:\ZhiMy\Code\git\xiaowang
如需删除远程 dev 分支:
1 git push origin --delete dev
相关链接