Merge与Rebase实战演示

Merge 与 Rebase 实战演示

概述

本笔记通过模拟两个开发者协作的方式,实际演示 git mergegit 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\git
cd E:\ZhiMy\Code\git

1.2 Clone 两份仓库(模拟两个人)

1
2
3
4
5
6
7
# --- 我的电脑 ---
git clone https://gitee.com/Zmmmmy/notes.git me
# 输出:Cloning into 'E:\ZhiMy\Code\git\me'...

# --- 小王的电脑 ---
git clone https://gitee.com/Zmmmmy/notes.git xiaowang
# 输出:Cloning into 'E:\ZhiMy\Code\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
# 输出:Switched to a new branch 'dev'

git push -u origin dev
# 输出:
# branch 'dev' set up to track 'origin/dev'.
# * [new branch] dev -> dev
1
2
3
4
5
# --- 小王的电脑 ---
cd E:\ZhiMy\Code\git\xiaowang

git fetch origin
git checkout dev

1.4 确认初始状态

1
2
3
4
5
# 两边都执行
git branch
# 输出:
# * dev
# main

此时三边状态一致:

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\xiaowang

echo "小王写的功能代码" > 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.txt
9dac2d9..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\me

echo "我写的登录代码" > 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 --- B
我本地 dev: A --- C ← 我有 C,但没有 B,也没 push
小王本地 dev: A --- B

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
# fetch
From https://gitee.com/Zmmmmy/notes
* branch dev -> FETCH_HEAD
9dac2d9..0324220 dev -> origin/dev

# rebase
Successfully rebased and updated refs/heads/dev.

rebase 把我的 C “搬”到了 B 后面,变成 C’:

1
2
3
远程 dev:     A(9dac2d9) --- B(0324220)
我本地 dev: A(9dac2d9) --- B(0324220) --- C'(86d4af7) ← C(2a4a8ad) 被复制到 B 后面,hash 变了
小王本地 dev: A(9dac2d9) --- 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..86d4af7  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://gitee.com/Zmmmmy/notes
* branch dev -> FETCH_HEAD
0324220..86d4af7 dev -> origin/dev
Updating 0324220..86d4af7
Fast-forward
login.txt | 1 +
1 file changed, 1 insertion(+)
create mode 100644 login.txt

最终三边一致:

1
2
3
远程 dev:     A(9dac2d9) --- B(0324220) --- C'(86d4af7)
我本地 dev: A(9dac2d9) --- B(0324220) --- C'(86d4af7)
小王本地 dev: A(9dac2d9) --- B(0324220) --- C'(86d4af7)

2.5 查看历史:一条干净的直线

1
git log --oneline --graph

实际输出:

1
2
3
4
* 86d4af7 (HEAD -> dev, origin/dev) C: 我添加登录功能
* 0324220 B: 小王添加功能
* 9dac2d9 (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 dev

# --- 小王的电脑 ---
cd E:\ZhiMy\Code\git\xiaowang
git checkout dev
git fetch origin
git reset --hard origin/dev

此时三边回到一致状态:

1
2
3
远程 dev:     A(9dac2d9)
我本地 dev: A(9dac2d9)
小王本地 dev: A(9dac2d9)

3.1 我先提交并 push

1
2
3
4
5
6
7
# --- 我的电脑 ---
cd E:\ZhiMy\Code\git\me

echo "我写的登录代码" > 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
9dac2d9..ee3309e dev -> dev

此时状态:

1
2
3
远程 dev:     A(9dac2d9) --- B(ee3309e)
我本地 dev: A(9dac2d9) --- B(ee3309e)
小王本地 dev: A(9dac2d9) ← 小王还不知道有 B

3.2 小王 pull 后也提交并 push

1
2
3
4
5
# --- 小王的电脑 ---
cd E:\ZhiMy\Code\git\xiaowang

# 小王先拉取最新代码,拿到了我的 B
git pull origin dev

实际输出:

1
2
3
4
5
6
7
8
From https://gitee.com/Zmmmmy/notes
* branch dev -> FETCH_HEAD
9dac2d9..ee3309e dev -> origin/dev
Updating 9dac2d9..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(9dac2d9) --- B(ee3309e) --- C(bc6a152)
我本地 dev: A(9dac2d9) --- B(ee3309e) ← 我还没拉 C
小王本地 dev: A(9dac2d9) --- 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\me

# 修改 login.txt 的内容
echo "我写的登录代码(修改版)" > login.txt
git add login.txt

# 用 --amend 改写上一个提交
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(9dac2d9) --- B(ee3309e) --- C(bc6a152)
我本地 dev: A(9dac2d9) --- B'(7bf219b) ← B 被改写成 B'hash 变了!
小王本地 dev: A(9dac2d9) --- 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

# 普通 push 会被拒绝,因为本地历史和远程不一致
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
# 于是用 --force 强推
git push --force origin dev

实际输出:

1
+ bc6a152...7bf219b dev -> dev (forced update)

此时状态:

1
2
3
远程 dev:     A(9dac2d9) --- B'(7bf219b)                  ← 被我强制覆盖!B 和 C 都没了!
我本地 dev: A(9dac2d9) --- B'(7bf219b)
小王本地 dev: A(9dac2d9) --- 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...7bf219b 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(9dac2d9) --- B(ee3309e) --- C(bc6a152)
我本地 dev: A(9dac2d9) --- B(ee3309e) ← 我还没拉 C
小王本地 dev: A(9dac2d9) --- B(ee3309e) --- C(bc6a152)

4.1 我用 merge 同步(正确做法)

1
2
3
4
5
# --- 我的电脑 ---
cd E:\ZhiMy\Code\git\me

# pull 默认就是 fetch + merge
git pull origin dev

实际输出:

1
2
3
4
5
6
7
From https://gitee.com/Zmmmmy/notes
* branch dev -> FETCH_HEAD
Updating 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(9dac2d9) --- B(ee3309e) --- C(bc6a152)
我本地 dev: A(9dac2d9) --- B(ee3309e) --- C(bc6a152) ← 直接快进合并
小王本地 dev: A(9dac2d9) --- B(ee3309e) --- C(bc6a152)

4.2 查看历史:干净且无重复

1
git log --oneline --graph

实际输出:

1
2
3
4
* bc6a152 (HEAD -> dev, origin/dev) C: 小王添加功能
* ee3309e B: 我添加登录功能
* 9dac2d9 (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

相关链接

  • 安全场景
  • 危险场景

Merge与Rebase实战演示
https://zmmmmy.github.io/2026/02/10/Merge与Rebase实战演示/
作者
ZhiMy
发布于
2026年2月10日
许可协议