适合学习理解的 Git 变基(Rebase)案例
为了帮助你更好地理解 Git 变基(Rebase)的操作和效果,下面通过一个简单的案例来演示变基的过程和影响。
案例背景
假设我们有一个 Git 仓库,包含两个分支:
- 主分支(main):包含最新的稳定代码。
- 特性分支(feature):开发新功能的分支,从
main
分支的某个旧提交创建。
初始提交历史
A --- B --- C --- D (main)\E --- F --- G (feature)
- A、B、C、D:
main
分支的提交。 - E、F、G:
feature
分支的提交,基于main
分支的提交B
。
场景
在 feature
分支开发期间,main
分支有了新的提交 C
和 D
。现在,我们希望将 feature
分支的变更应用到 main
分支的最新提交 D
之后,使提交历史更加线性。
变基操作步骤
1. 切换到 feature
分支
git checkout feature
2. 执行变基操作
git rebase main
3. Git 的操作过程
- 找到共同祖先:Git 找到
feature
分支和main
分支的共同祖先提交B
。 - 应用提交:将
feature
分支上的每个提交(E
、F
、G
)依次应用到main
分支的最新提交D
之后。 - 解决冲突(如果有):如果在应用提交时遇到冲突,需要手动解决冲突后继续变基。
4. 变基完成后的提交历史
A --- B --- C --- D (main)\E' --- F' --- G' (feature)
- E’、F’、G’:变基后的新提交,基于
main
分支的最新提交D
。 - 原提交 E、F、G:被新提交 E’、F’、G’ 替代,提交哈希值发生变化。
详细步骤演示
步骤 1:创建初始提交历史
# 初始化仓库
git init# 创建 main 分支并提交
git checkout -b main
echo "Initial commit" > file.txt
git add file.txt
git commit -m "A"echo "Second commit" >> file.txt
git add file.txt
git commit -m "B"echo "Third commit" >> file.txt
git add file.txt
git commit -m "C"echo "Fourth commit" >> file.txt
git add file.txt
git commit -m "D"# 创建 feature 分支并提交
git checkout -b feature HEAD~3 # 基于提交 B 创建分支
echo "Feature commit 1" >> file.txt
git add file.txt
git commit -m "E"echo "Feature commit 2" >> file.txt
git add file.txt
git commit -m "F"echo "Feature commit 3" >> file.txt
git add file.txt
git commit -m "G"
步骤 2:执行变基操作
# 切换到 feature 分支
git checkout feature# 变基到 main 分支
git rebase main
步骤 3:查看变基后的提交历史
# 查看提交历史
git log --oneline --graph --all
输出示例:
* 1234567 (HEAD -> feature) G' - Feature commit 3
* 2345678 F' - Feature commit 2
* 3456789 E' - Feature commit 1
* 4567890 (main) D - Fourth commit
* 5678901 C - Third commit
* 6789012 B - Second commit
* 7890123 A - Initial commit
案例解析
1. 变基前
feature
分支的提交E
、F
、G
基于main
分支的旧提交B
。- 提交历史呈现分支结构,不够线性。
2. 变基后
feature
分支的提交E'
、F'
、G'
基于main
分支的最新提交D
。- 提交历史变得线性,易于阅读和理解。
3. 提交哈希值变化
- 变基会重写提交历史,导致提交哈希值发生变化(如
E
变为E'
)。 - 这也是变基不适用于已推送的共享分支的原因。
类比理解
类比:
想象你正在写一本小说,main
分支是主线剧情,feature
分支是支线剧情。
- 变基前:支线剧情基于主线剧情的某个旧章节。
- 变基后:你将支线剧情的修改应用到主线剧情的最新章节之后,使整个故事更加连贯。
注意事项
-
不要在已推送的共享分支上使用
rebase
:
如果feature
分支已经推送到远程仓库,并且其他开发者基于该分支进行了开发,使用rebase
会导致提交历史不一致,引发问题。 -
备份分支:
在进行rebase
操作前,建议创建分支备份,以防出现问题。 -
理解提交历史:
rebase
会改变提交历史,需要理解其影响,避免误操作。
总结
- 变基(Rebase):将当前分支的变更应用到目标分支的最新提交之后,使提交历史更加线性。
- 案例演示:通过
feature
分支变基到main
分支,展示了变基的操作和效果。 - 适用场景:本地开发分支整合、清理提交历史。
- 注意事项:如果分支已经推送到远程仓库,其他开发者可能已经基于该分支的提交进行了开发。变基后,提交历史发生变化,导致其他开发者的本地仓库与远程仓库不一致。 不要对已经推送的记录进行变基!
变基之后仍然存在两个分支,但 feature 分支的基准点(base)发生了变化。具体来说,feature 分支原来是基于 main 分支的提交 B,变基后变成了基于 main 分支的最新提交 D。
图片中, 右边 在dev分支上 变基
变基后合并操作的步骤
1. 前提条件
- 已完成变基:假设你已经对某个分支(如
feature
分支)执行了git rebase main
,使其基于main
分支的最新提交。 - 目标分支:你希望将变基后的
feature
分支合并到main
分支。
2. 切换到目标分支
git checkout main
- 目的:确保你在要合并到的目标分支上。
3. 更新目标分支
git pull origin main
- 目的:确保目标分支是最新的,避免合并冲突。
4. 合并变基后的分支
git merge feature
- 情况 1:如果变基后没有冲突,Git 会自动完成合并,生成一个合并提交(如果使用
--no-ff
选项)。 - 情况 2:如果有冲突,Git 会提示你解决冲突。
5. 解决冲突(如果有)
- 查看冲突文件:
git status
- 手动编辑冲突文件:解决冲突后,标记冲突已解决:
git add <conflicted-file>
- 完成合并:
git commit
6. 推送更新到远程仓库
git push origin main
- 目的:将合并后的更改推送到远程仓库。
变基后合并的注意事项
-
确保变基已完成且无冲突:
- 在合并前,确保
feature
分支已经成功变基到main
分支的最新提交。 - 解决变基过程中可能出现的冲突。
- 在合并前,确保
-
避免在已推送的共享分支上变基:
- 如果
feature
分支已经推送到远程仓库,并且其他开发者基于该分支进行了开发,变基可能导致问题。 - 建议:在本地分支上变基,合并前与其他开发者沟通。
- 如果
-
使用
--no-ff
选项(可选):- 合并时可以使用
--no-ff
(no fast-forward)选项,保留分支历史,便于查看合并记录。
git merge --no-ff feature
- 合并时可以使用
-
备份重要分支:
- 在进行合并操作前,建议备份重要分支,以防出现问题。
变基后合并的优势
- 线性提交历史:变基后,提交历史更加线性,易于阅读和理解。
- 减少合并提交:如果变基成功,合并时可能不会产生额外的合并提交(取决于是否使用
--no-ff
)。 - 清晰的开发流程:通过变基和合并,可以保持代码库的整洁和一致性。
变基后合并的示例
场景描述
- 初始状态:
main
分支:A --- B --- C --- D
feature
分支:A --- B --- E --- F --- G
(基于B
)
- 变基后:
feature
分支:A --- B --- C --- D --- E' --- F' --- G'
(基于D
)
合并操作
-
切换到
main
分支:git checkout main
-
更新
main
分支:git pull origin main
-
合并
feature
分支:git merge feature
-
解决冲突(如果有):
- 编辑冲突文件,标记解决,完成合并。
-
推送更新:
git push origin main
-
最终提交历史:
A --- B --- C --- D --- E' --- F' --- G' (main, feature)