前言
在现代软件开发中,Git 是我们管理代码、协同工作的基石。我们每天都在使用它,但我们真的了解它吗?本文记录了一位开发者从一个看似简单的“分支合并”需求开始,逐步深入的完整过程。
这不仅仅是一篇操作指南,更是一次思维的升级。我们将从最基础的跨仓库分支合并开始,途经选择性集成的 cherry-pick,深入探讨冲突解决的细节,揭示 cherry-pick 背后令人困惑的三方合并(3-way merge)机制,并最终掌握“外科手术式”的代码移植大法——补丁(Patch)工作流。
无论你是 Git 新手,还是已经有一定经验的开发者,相信这次旅程都能让你对 Git 的理解更上一层楼。
第一章:最初的需求 —— 跨仓库分支合并
我们的旅程始于一个经典场景:如何将一个上游主仓库(yourrepository/yourrepository)的 2025lts 分支上的修改,合并到我们自己的开发仓库(yourbranchdev/yourrepository)的 yourbranchdev 分支上?
核心思路:在本地克隆的目标仓库中,添加一个指向源仓库的“远程源”(Remote),然后抓取(Fetch)更新,最后在本地进行合并(Merge)。
方案A:经典的命令行操作
-
准备本地环境:克隆你的目标仓库,并切换到目标分支。
# 克隆你的目标仓库
git clone https://git-yourrepository.yoururl.com/yourbranchdev/yourrepository.git
cd yourrepository
# 切换到你的目标分支
git checkout yourbranchdev
# 确保分支是最新版本
git pull origin yourbranchdev -
添加源仓库为新的远程仓库:我们按照社区惯例,将其命名为
upstream。
# 添加名为 upstream 的新远程仓库
git remote add upstream https://git-yourrepository.yoururl.com/yourrepository/yourrepository.git
# 验证是否添加成功
git remote -v -
拉取源仓库的更新:
fetch命令只会下载数据,不会自动合并。
git fetch upstream执行后,源分支
2025lts在你本地的引用名为upstream/2025lts。 -
执行合并:使用
--no-ff参数可以创建一个明确的合并提交记录,便于追溯历史。
git merge --no-ff upstream/2025lts -
解决冲突与推送:如果出现冲突,手动解决文件中的
<<<<<<<,=======,>>>>>>>标记,然后git add <文件名>并git commit。最后,将合并后的代码推送到你自己的远程仓库。
git push origin yourbranchdev
方案B:直观的 TortoiseGit 图形化操作
对于习惯图形界面的用户,TortoiseGit 提供了同样清晰的操作路径。
-
准备本地环境:右键仓库文件夹 -> TortoiseGit -> Switch/Checkout... 切换到
yourbranchdev分支,然后执行一次 Pull 操作。 -
添加远程仓库 (
upstream): -
右键仓库文件夹 -> TortoiseGit -> Settings -> Git -> Remote。 -
在 "Remote" 文本框中输入 upstream,在 "URL" 中输入源仓库地址。 -
点击 Add New/Save。 -
抓取 (Fetch) 更新:
-
右键 -> TortoiseGit -> Fetch...。 -
在弹出的对话框中,将 "Remote" 从 origin改为upstream,然后点击 "OK"。 -
执行合并 (Merge):
-
右键 -> TortoiseGit -> Merge...。
-
在 "Select the branch to merge from" 部分,点击分支选择按钮,在
remotes/upstream下找到并选择2025lts。 -
勾选 "No Fast Forward" 选项。
-
点击 "OK" 开始合并。
-
解决冲突与推送:
-
如果遇到冲突,在冲突文件上右键 -> TortoiseGit -> Edit Conflicts,使用合并工具解决。
-
解决后,在文件上右键 -> TortoiseGit -> Resolved。
-
所有冲突解决后,执行 Git Commit。
-
最后,右键 -> TortoiseGit -> Push...,确保推送到
origin的yourbranchdev分支。
第二章:新的挑战 —— 我只想合并某些修改
简单的分支合并很快遇到了新问题:如果我不想合并 2025lts 上的所有修改,而只是需要其中一两个特定的功能或修复呢?
这就引出了 Git 的另一个强大工具:cherry-pick (拣选)。
核心思路:从源分支上“摘取”一个或多个特定的提交(Commit),然后像打补丁一样把这些提交应用到当前分支。
方案A:命令行的 git cherry-pick
-
准备工作:同第一章,确保已添加
upstream并fetch。 -
找到 Commit Hash:使用
git log upstream/2025lts或在 GitLab 网页上找到你想要合并的提交的唯一ID(Commit Hash),例如a1b2c3d4。 -
执行 Cherry-pick:
# 确保当前在 yourbranchdev 分支
git checkout yourbranchdev
# 按顺序拣选你想要的提交
git cherry-pick a1b2c3d4 f1e2d3c4 -
解决冲突:如果冲突,手动解决后
git add <文件名>,然后git cherry-pick --continue继续。 -
推送:
git push origin yourbranchdev。
方案B:TortoiseGit 的可视化拣选
-
打开日志浏览器:右键仓库文件夹 -> TortoiseGit -> Show Log。勾选左下角的 "All Branches" 复选框,确保能看到
remotes/upstream/2025lts的提交。 -
执行 Cherry-pick:
-
在日志列表中,按住
Ctrl键多选你想要的提交。 -
在选中的提交上右键 -> Cherry-pick selected commits...。
-
在弹出的确认窗口中点击 "Continue"。
-
解决冲突与推送:解决冲突的流程与 Merge 操作类似,完成后 Push 即可。
第三章:深入冲突区 —— 细节决定成败
在执行 cherry-pick 的过程中,我们遇到了冲突。为了高效、正确地解决它们,理解工具的每一个细节至关重要。
3.1 冲突面板的身份之谜:HEAD vs CHERRY_PICK_HEAD
当冲突发生时,冲突解决面板会展示两个版本,它们分别是谁?
|
|
|
|
|
|---|---|---|---|
HEAD |
|
yourbranchdev 分支
|
"Mine"
|
CHERRY_PICK_HEAD |
|
2025lts 分支上拣选的那个提交所带来的修改
|
"Theirs"
|
简单来说,HEAD 是你的代码,CHERRY_PICK_HEAD 是你想拿过来的代码。你的任务,就是决定如何将“他们的”修改正确地融入“你的”代码中。
3.2 读懂合并工具的语言:= 符号是什么意思?
在 TortoiseGitMerge 的对比界面中,每一行前面的符号都是它在向你“说话”。
|
|
|
|
|---|---|---|
= |
|
相同 (Identical)
Theirs 和 Mine 两个版本中完全一样。这是你的朋友,帮你排除干扰,告诉你“这里没问题”。
|
! |
|
已更改 (Changed/Modified)
|
+ |
|
已添加 (Added)
|
- |
|
已删除 (Deleted)
|
你的任务就是:忽略所有带 = 的行,专注于处理那些带 !、+、- 符号的冲突块。
3.3 “我该点 Commit 吗?”—— rebasing... (1/4) 场景解析
在用 TortoiseGit 进行 cherry-pick 并解决完一个冲突后,你可能会看到界面提示 rebasing... (1/4),并有一个 "Commit" 按钮。这让许多人感到困惑:我现在提交,是提交到远程了吗?我还没测试呢!
答案是:放心地点击 "Commit"!
-
rebasing... (1/4)的含义:这表示你当初选择了拣选4个提交,Git 正尝试按顺序应用它们。现在它在应用第1个时遇到了冲突,你刚刚解决了它。 -
"Commit" 按钮的作用:在这里,它不等同于创建一个新提交,而是相当于执行
git cherry-pick --continue。它的意思是:“第1个提交的冲突我解决了,请完成它的应用,然后继续尝试应用第2个。” -
本地提交 vs 远程推送:这个操作只会提交到你本地的
yourbranchdev分支。只要你没有执行 Push,所有操作都只发生在你的个人电脑上,是一个安全的“沙盒”。 -
正确的测试流程:
-
不断重复“解决冲突 -> 点击 Commit”的循环,直到所有(4个)提交都成功应用。
-
当整个流程结束后,在你的本地电脑上进行充分的编译、运行和测试。
-
测试通过后,才执行最后一步:
TortoiseGit -> Push...,将这些已经验证过的、合并好的提交推送到远程仓库。
第四章:cherry-pick 的惊人真相 —— 为什么它会带入我不想的代码?
就在我们以为已经掌握了 cherry-pick 时,一个更深层次的问题浮出水面:我明明拣选了一个只修改了几行代码的提交,为什么最终结果却把那个文件相对于我本地的所有差异都应用上了,带入了很多我根本不想要的修改?
核心原因:cherry-pick 并非简单的“复制粘贴补丁”,而是在尝试“重演(Replay)”变更。为此,它使用了 Git 的核心机制——三方合并(3-way merge)。
当 cherry-pick 一个 Commit-B 时,Git 会找到三方进行对比:
-
BASE (共同祖先):
Commit-B的父提交Commit-A。 -
THEIRS (他们的):
Commit-B本身。 -
MINE (我的):你当前分支
HEAD的状态。
问题就出在这里:假设 Commit-A 包含了一些我们不想要的修改。当 Git 对比 BASE(Commit-A) 和 THEIRS(Commit-B) 时,它计算出的变更是正确的。但当它把这个变更应用到 MINE 上时,它会进行一次完整的三方合并。对于那些在 Commit-A 中被修改、而在 MINE 中还是原始状态的代码行,Git 会认为 THEIRS (它也包含了Commit-A的修改)的版本是“更新”的,因此会把那些我们不想要的、来自 Commit-A 的修改也一并带了进来!
一句话总结:cherry-pick 的本质是请求 Git 将 Commit-B 相对于其父提交 Commit-A 的变化应用到你的分支。这个过程中,Git 会把 Commit-A 当作“背景”,导致 Commit-A 引入的、而你的分支又没有的变更,被一同带了进来。
第五章:外科手术式操作 —— 补丁 (Patch) 工作流
既然 cherry-pick 的默认行为无法满足我们“纯粹移植”的需求,我们就需要一种能绕过三方合并机制的方法。答案就是补丁(Patch)。
核心思路:将提交的“修改内容本身”(即 Diff)提取出来,做成一个独立的补丁文件,然后将这个纯粹的“修改说明书”应用到我们的分支上。
5.1 如何在 TortoiseGit 中创建并应用补丁?
-
找到源提交并创建补丁:
-
在 TortoiseGit 日志中,找到你想要的提交。
-
右键 -> Format Patch...。
-
选择一个目录保存生成的
.patch文件。 -
应用补丁:
-
切换回你的目标分支。
-
右键仓库文件夹 -> TortoiseGit -> Apply Patch Serial...。
-
选择你刚刚保存的
.patch文件。 -
检查并提交:
-
应用后,打开
TortoiseGit -> Commit...。 -
你会发现,只有那个提交自身带来的修改被应用了,所有历史包袱都被干净地甩掉了!
-
确认无误后,提交即可。
5.2 如何处理多个提交?
如果需要移植多个提交,这个方法同样适用,并且效率极高。
-
多选提交并创建系列补丁:
-
在日志中,按住
Ctrl或Shift多选你想要的提交。 -
右键 -> Create serial patch...。
-
选择一个文件夹来存放补丁。TortoiseGit 会为每个提交生成一个带编号的
.patch文件。 -
应用系列补丁:
-
右键 -> TortoiseGit -> Apply Patch Serial...。
-
这次,选择你保存补丁的那个文件夹。
-
TortoiseGit 会自动识别并按顺序应用所有补丁。
-
合并提交:
-
应用完成后,所有修改都在你的工作区。
-
在
Commit对话框中,推荐将这些来自多个补丁的修改合并成一个单独的、干净的提交,并写好提交信息,说明你移植了哪些功能。
5.3 “Show changes as unified diff” 又是什么?
这是一个非常有用的辅助功能。它实际上就是“创建补丁”方法的手动分解步骤。Unified Diff 格式本身就是补丁文件的内容标准。
-
Create serial patch...:是自动化工具,一键帮你完成“查看 diff -> 复制 -> 保存为文件”的全过程,高效且不易出错。 -
Show changes as unified diff:是信息展示工具,让你能预览 diff 内容,或者手动复制出来创建补丁。它更灵活,但操作繁琐,适合学习或只想复制一两行代码的场景。
结论:在需要“干净地”合并一个或多个提交时,Create serial patch... -> Apply Patch Serial... 是最精准、最可靠的工作流。
第六章:终极策略 —— 合并最佳实践与复杂依赖处理
我们的探索之旅来到了终点,讨论也上升到了战略层面。
6.1 分支合并的最佳姿势是什么?
没有银弹,只有最适合你团队的策略。主流选择是 Merge 和 Rebase。
-
Merge (
--no-ff):忠实记录历史,安全,适合将功能分支合并到公共主干。缺点是历史记录繁杂。 -
Rebase:获得线性的清爽历史。缺点是会重写历史,黄金法则:永远不要对公共分支执行 Rebase。
推荐的组合拳:
-
个人开发时:在自己的功能分支上,定期
rebase主干分支的更新,以保持自己的分支始终基于最新代码,减少未来的合并冲突。 -
功能完成后:将自己的功能分支
merge到主干分支,保留完整的开发轨迹。
6.2 终极挑战:如何处理跨提交的复杂依赖?
当 Commit-C 依赖了 Commit-A 的部分代码,但你又不想要 Commit-A 的其他内容时,应该怎么办?
答案:Patch + 手动筛选的外科手术。
-
识别依赖:这是最重要的一步,需要开发者对代码有清晰的理解,人工分析
Commit-C到底依赖了Commit-A的哪些代码。 -
提取纯粹变更:使用 "Create serial patch...",同时选中
Commit-A和Commit-C,生成两个补丁文件。 -
应用变更:使用 "Apply Patch Serial..." 将这两个补丁应用到你的工作目录。
-
“手术”开始:剥离不需要的变更:
-
打开
TortoiseGit -> Commit...,但不要提交。 -
双击被修改的文件,打开对比工具。
-
仔细审查来自
Commit-A.patch的每一处修改。对于那些非必需的变更,使用 "Revert this change" 手动撤销它们。 -
这个过程的目标是:保留
Commit-C的所有修改,以及Commit-A中被严格依赖的最小代码集,剔除其他所有无关修改。 -
创建原子性提交:
-
完成“手术”后,回到提交对话框。现在暂存区里的变更就是你最终想要的“
Commit-C+ 最小依赖集”。 -
写一个清晰的提交信息,详细说明你移植了什么功能,以及为了满足依赖从哪个提交中引入了什么代码。
-
提交!
这样,你就把一个复杂的、跨越多提交的逻辑功能,变成了一个干净、独立、原子性的提交,完美地融入了你的分支。
结语
从一个简单的合并请求出发,我们一路披荆斩棘,最终掌握了足以应对复杂场景的 Git 高级策略。这次旅程告诉我们,工具的操作是基础,而理解其背后的机制和原理,才能让我们在面对未知挑战时,有能力制定出最高效、最可靠的解决方案。希望这篇文章能成为你 Git 工具箱中一份宝贵的参考。
注:本文为与AI的共创。
-
米哈游 ZZZ 绝区零 渲染细节新发现 网友发现《黑神话:悟空》误将钢筋扫描至游戏中,如何评价游戏场景实景扫描这样的做法?是怎么办到的?
-
收藏转发,GPU、CPU、内存等150+游戏开发性能分析优化干货合集! -
Unity3D游戏开发中100+效果的实现和源码大全 - 收藏起来肯定用得着 -
非广告!6年老号福利,描边、景深、泛光、投影等60+游戏后处理效果实现合集!
声明:本文来自公众号:游戏开发技术教程(GameDevLearning),转载请附上原文链接及本声明。
关注【游戏开发技术教程】
游戏开发技术、技巧、教程和资源,答疑解惑,内推面试


