2.1.1 Fast-Forward 快进合并 当你要合并的两个分支满足一个前提 目标分支比如main在你创建特性分支

最后再强调一遍Git官方的最高警告 永远不要重写已经公开的提交历史 参考资料 Git官方文档 - git-merge: https://git-scm.com/docs/git-merge Git官方文档 - git-rebase: https://git-scm.com/docs/git-rebase Git官方Pro Git书籍 - 分支与变基: https://git-scm.com/book/en/v2/Git-Branching-Rebasing Git官方文档 - 交互式变基: https://git-scm.com/docs/git-rebase#_interactive_mode ,如果非要修改已经推送的公共分支提交必须用git revert生成反向提交而不是Rebase, 误区7Rebase之后原始提交就彻底消失了 纠正 Rebase之后原始提交只是没有被分支指针指向了并没有被立即删除, feature/login) feat: add login validation* 7c6d5b4 feat: add login core logic* 5a6b7c8 chore: add base config* a7c3d90 chore: init project3.3 Rebase 冲突处理 Rebase在逐个应用补丁的过程中如果遇到冲突会暂停当前的变基操作提示冲突文件此时你需要遵循以下正确步骤处理90%的开发者在这里都会操作错误 打开冲突文件手动解决所有冲突内容 解决完成后执行git add 冲突文件将修改后的文件加入暂存区 执行git rebase --continue继续应用下一个补丁 绝对不要执行git commit 如果想中途放弃变基恢复到变基前的状态执行git rebase --abort 这里必须强调Rebase的冲突是逐个提交处理的如果你的特性分支有10个提交可能需要解决10次冲突这是Rebase的一个缺点而Merge只需要解决一次冲突, 误区2Rebase比Merge高级用Rebase就是更专业 纠正 Merge和Rebase只是两个不同的工具有不同的适用场景没有高低优劣之分, 七、企业级团队协作最佳实践 基于Git官方推荐结合业界主流的Git Flow、GitHub Flow、GitLab Flow规范我们整理了一套可直接落地的、零坑的合并策略最佳实践无论是小团队还是大型企业都可以直接使用 7.1 分支管理与合并策略匹配规范分支类型分支命名核心用途合并策略禁止操作 主干分支 main/master 存放可部署的生产代码永远保持稳定 仅接受PR/MR合并必须使用git merge --no-ff禁止直接提交 禁止Rebase、禁止强制推送、禁止直接提交 特性分支 feature/* 开发者本地开发新功能 开发过程中用git rebase main同步主干代码用git rebase -i整理提交历史合并回主干用Merge --no-ff 禁止Rebase已经推送到远程的共享特性分支 发布分支 release/* 版本发布前的测试、预发布 用Merge合并特性分支修复bug直接提交 禁止Rebase、禁止强制推送 热修复分支 hotfix/* 生产环境紧急bug修复 开发完成后用Merge同时合并回main分支和release分支 禁止Rebase、禁止强制推送 7.2 提交历史管理规范 本地开发过程中允许生成临时提交但在提交PR/MR之前必须用交互式变基整理提交历史确保每个提交都是原子性的一个提交只做一件事提交信息符合Conventional Commits规范 禁止将多个不相关的功能修改放在同一个提交中 禁止在主干分支中生成无意义的合并提交所有合并到主干的操作都必须有明确的合并目的 7.3 远程操作规范 绝对禁止对公共分支执行git push --force仅允许对自己的私有特性分支使用git push --force-with-lease强制推送--force-with-lease会检查远程分支是否有新的提交避免覆盖其他人的修改比--force安全得多 拉取远程分支更新时推荐使用git pull --rebase或者配置全局默认pull用rebase避免本地分支生成大量无意义的合并提交 推送代码之前必须先拉取远程分支的最新更新解决所有冲突再推送 7.4 冲突处理规范 合并冲突必须由代码的修改人亲自解决禁止不看代码直接用--ours或--theirs覆盖 解决冲突后必须编译测试通过再继续合并或变基操作 Rebase过程中如果冲突过多难以解决可以执行git rebase --abort放弃变基改用Merge合并 7.5 代码评审与合并规范 PR/MR提交之前必须先Rebase同步主干的最新代码解决所有冲突确保评审的代码是基于最新主干的 代码评审通过后由项目负责人执行合并操作必须使用git merge --no-ff禁止快进合并确保完整保留特性分支的开发轨迹 合并完成后及时删除已经合并的远程特性分支避免分支冗余 八、最终总结 回到最开始的灵魂拷问到底用Merge还是Rebase 答案非常简单记住两句话就够了 公共分支合并用Merge安全完整可追溯私有分支整理用Rebase干净线性易阅读, 2.2 Merge的核心特性 绝对不修改现有提交历史 所有已存在的提交的哈希值、内容、父节点都不会有任何变化只会新增合并提交这是Merge最大的优势也是协作安全性的核心保障 完整保留开发轨迹 合并提交的双父节点结构完整记录了分支的分叉和合并过程可追溯性极强方便后续排查问题和回滚 冲突仅需解决一次 无论两个分支有多少差异只需要在生成合并提交时解决一次冲突操作成本低 分支历史会出现分叉 多次合并后提交历史会出现大量分叉长期下来会变成“蜘蛛网”可读性变差 2.3 Merge的核心参数与高频用法 --no-ff强制关闭快进合并即使可以快进也强制生成合并提交, 线性化本地分支历史提升可读性 对于你自己的私有分支用Rebase保持提交历史线性方便后续排查问题、回溯代码修改, 误区4对公共分支Rebase后只要强制推送就行 纠正 对公共分支执行Rebase后哪怕用git push --force强制覆盖了远程分支也无法解决根本问题, 这个底层逻辑是所有Git操作的基础接下来的Merge和Rebase都是基于这个核心逻辑实现的, 而HEAD指针本质是一个指向当前所在分支的指针你切换分支本质只是改变了HEAD的指向然后把工作区更新为对应分支指向的提交快照。

5.4 绝对禁止使用Rebase的场景 任何公共分支main/develop/release等绝对禁止执行Rebase 已经推送到远程仓库、且有其他协作者使用的分支绝对禁止执行Rebase 已经合并到公共主干的提交绝对禁止执行Rebase 多人协作的共享分支绝对禁止执行Rebase 六、高频误区与易混淆点避坑 这里整理了开发者最容易踩的10个误区100%纠正错误认知避免踩坑 误区1Rebase会丢失提交历史Merge不会 纠正 Rebase不会丢失任何提交内容它只是把原始提交废弃生成了全新的提交所有的代码修改都会完整保留只是提交的哈希值和父节点变了历史变成了线性的, 合并多个分支的复杂场景 当需要一次性合并多个分支时Merge的octopus策略可以轻松处理而Rebase无法实现, 5.2 什么时候必须用Merge 满足以下任意一个场景优先选择Merge绝对不要用Rebase 将特性分支合并回公共主干分支main/develop 这是Merge最核心的使用场景,所有的合并操作本质都是对提交对象和分支指针的操作, 即使你用git push --force强制覆盖了远程分支也无法解决这个问题只会让灾难扩散,此时两个分支的提交历史是线性的Git不需要生成新的合并提交只需要把目标分支的指针直接移动到特性分支的最新提交上就完成了合并这就是快进合并, 误区10三方合并就是简单的两个分支内容对比覆盖 纠正 Git的三方合并是基于共同祖先的差异合并不是简单的覆盖, Git Merge和Rebase不是对立的两个选项而是互补的两个工具,你创建一个新分支本质只是创建了一个新的指针文件里面存储了目标提交的哈希值几乎没有任何性能开销,有人无脑用Merge导致仓库提交历史分叉成“蜘蛛网”回滚时无从下手有人盲目跟风用Rebase结果重写了公共分支历史把整个团队的协作流程搞崩甚至弄丢了线上代码,Merge的核心价值是安全和可追溯是团队协作的基础保障Rebase的核心价值是整洁和灵活是本地开发的效率工具。

很多资深开发者在公共分支合并时永远只用Merge因为安全永远是第一位的,这个合并提交有一个特殊的结构它有两个parent指针分别指向两个待合并分支的最新提交以此完整保留两个分支的开发历史,用对了场景两个都是专业的操作用错了场景哪怕是Rebase也是灾难性的错误。

一、前置基础Git分支的底层核心逻辑 要彻底搞懂Merge和Rebase必须先理解Git的核心设计—— Git是一个基于快照的分布式版本控制系统分支本质是指向提交对象的可变指针 , 提交PR/MR之前解决与主干的冲突 在提交代码评审之前用Rebase同步主干最新代码解决所有冲突确保评审的代码是基于最新主干的避免评审完成后合并时出现冲突, 它的基础用法是git rebase -i 目标提交其中目标提交是你要整理的提交范围的父提交比如git rebase -i HEAD~3就是整理最近3个提交, 2.1.2 Three-Way Merge 三方合并 这是Merge最常用的场景当两个分支满足 目标分支和特性分支在分出之后都有了新的提交 此时两个分支的历史出现了分叉无法执行快进合并Git会自动执行三方合并, 5.3 什么时候可以用Rebase 满足以下所有前提条件才可以使用Rebase缺一不可 操作的分支是你自己的本地私有分支 没有推送到远程仓库或者推送到了远程但只有你一个人在使用没有任何其他协作者基于这个分支开发 你要重写的提交没有被合并到任何公共分支 满足前提的情况下以下场景是Rebase的最佳实践 同步公共主干分支的最新提交到本地特性分支 当你在本地开发特性分支时主干分支有了新的提交你需要同步这些提交来解决冲突、避免后续合并出现问题, 三方合并的执行流程用Mermaid流程图表示如下 执行以下命令可完整复现三方合并场景 # 基于上面的仓库重置main分支到初始提交git reset --hard a7c3d90# 删除旧的特性分支git branch -D feature/docs# 重新创建特性分支git checkout -b feature/login# 特性分支第一次提交mkdir srcecho // user login core logic  src/login.jsgit add src/login.jsgit commit -m feat: add login core logic# 特性分支第二次提交echo // login input validation  src/login.jsgit add src/login.jsgit commit -m feat: add login validation# 切回main分支提交新内容制造分叉git checkout mainecho // project base config  config.jsgit add config.jsgit commit -m chore: add base config# 执行三方合并git merge feature/login 如果没有冲突Git会自动生成合并提交并打开编辑器让你填写合并提交信息默认信息为Merge branch feature/login,其他协作者的本地分支还是基于旧的历史他们pull的时候会生成大量重复提交导致历史彻底混乱, 执行命令后Git会打开编辑器列出你要整理的所有提交每个提交前面有一个操作命令默认是pick你可以修改命令来实现不同的操作核心命令如下 命令缩写功能说明 pick p 保留该提交不做任何修改 reword r 保留提交内容修改提交信息 edit e 保留提交内容暂停变基允许你修改提交的内容 squash s 将该提交合并到上一个提交中保留两个提交的信息 fixup f 将该提交合并到上一个提交中丢弃该提交的信息 drop d 删除该提交彻底丢弃该提交的内容 实战示例合并最近2个零散的提交整理成一个完整的功能提交 # 基于上面的feature/login分支执行交互式变基git checkout feature/logingit rebase -i HEAD~2 执行后编辑器会打开如下内容 pick 7c6d5b4 feat: add login core logicpick 8d9e0f1 feat: add login validation# 命令说明... 我们将第二个提交的命令改为squash修改后如下 pick 7c6d5b4 feat: add login core logicsquash 8d9e0f1 feat: add login validation 保存退出后Git会打开新的编辑器让你填写合并后的提交信息修改为feat: complete user login function with validation保存后完成变基,Git会对比两个分支相对于共同祖先的修改只要两个分支修改的不是同一行代码Git会自动合并只有修改了同一行代码才会提示冲突,这也是Git合并能力强大的核心原因,Fast-Forward合并只是移动了分支指针没有修改任何提交所有提交的哈希值完全不变而Rebase是生成了全新的提交哈希值完全改变原始提交被废弃,如果你想让pull的时候默认用Rebase可以执行配置命令git config --global pull.rebase true这也是很多团队的推荐配置可以避免本地分支生成大量无意义的合并提交,这是企业级团队最常用的参数能完整保留特性分支的开发轨迹方便后续回滚和追溯示例git merge --no-ff feature/login --squash压缩合并将特性分支的所有提交压缩成一个新的提交合并到目标分支不会保留特性分支的提交历史也不会生成双父节点的合并提交适合清理临时分支的零散提交 --abort合并出现冲突时终止合并操作恢复到合并前的状态 三、Git Rebase 深度解析线性化历史的变基操作 Git Rebase的官方定义是 在另一个基础提交的顶端重新应用一系列提交 , 误区8合并冲突的时候Rebase和Merge的处理方式是一样的 纠正 Merge冲突解决完成后需要执行git commit生成合并提交而Rebase冲突解决完成后需要执行git add然后git rebase --continue绝对不能执行git commit否则会生成额外的提交导致变基失败, 执行git log --oneline查看原本的2个提交已经合并成了1个完整的提交提交历史更加整洁, 误区3Fast-Forward合并和Rebase的结果是一样的 纠正 两者的最终提交历史看起来都是线性的但底层逻辑完全不同。

Merge和Rebase没有绝对的优劣只有是否适合场景用对了事半功倍用错了灾难连连, 原因非常简单你对公共分支执行Rebase后重写了提交历史远程仓库的提交哈希发生了变化而其他协作者的本地仓库还是基于旧的提交历史开发的, # 初始化Git仓库并配置用户信息git init merge-rebase-democd merge-rebase-demogit config user.name Demo Usergit config user.email demoexample.com# 主干分支初始提交LCA共同祖先echo # Merge vs Rebase Demo  README.mdgit add README.mdgit commit -m chore: init project# 创建并切换到特性分支feature/docsgit checkout -b feature/docs# 特性分支提交新内容echo ## Quick Start  README.mdgit add README.mdgit commit -m docs: add quick start guide# 切回主干分支main此时main无新提交git checkout main# 执行快进合并git merge feature/docs 合并完成后执行git log --oneline --graph查看提交历史输出如下 * 45f8d21 (HEAD - main, 误区9Rebase可以替代Merge 纠正 绝对不能, 1.1 Git提交对象的核心结构 Git中的每一次提交commit都是一个不可变的快照对象包含4个核心信息所有内容共同生成唯一的SHA-1哈希值 tree对象 指向当前提交的文件目录树快照记录所有文件的内容和结构 parent对象 指向当前提交的父提交也就是上一次提交的哈希值初始提交无父对象 作者信息 代码修改人的姓名、邮箱和修改时间 提交者信息 执行提交操作的人的姓名、邮箱和提交时间 这里的核心关键点 只要提交的parent、内容、元数据有任何一点变化生成的SHA-1哈希值就会完全改变变成一个全新的提交对象 ,任何团队都不可能只用Rebase不用Merge否则协作一定会出问题,唯一正确的做法是永远不要对公共分支执行Rebase, 3.1 Rebase的底层执行逻辑 很多人对Rebase的理解停留在“让提交历史变直”但没有搞懂它的底层操作这也是绝大多数坑的根源, 2.1.1 Fast-Forward 快进合并 当你要合并的两个分支满足一个前提 目标分支比如main在你创建特性分支之后没有任何新的提交 , Rebase的完整执行流程用流程图表示如下 3.2 Rebase 基础实战示例 我们用和上面三方合并完全相同的仓库场景来演示Rebase的操作让你直观看到和Merge的区别 # 基于之前的仓库重置main分支到初始提交git reset --hard a7c3d90# 删除旧的特性分支git branch -D feature/login# 重新创建特性分支和Merge示例完全一致的提交git checkout -b feature/loginmkdir srcecho // user login core logic  src/login.jsgit add src/login.jsgit commit -m feat: add login core logicecho // login input validation  src/login.jsgit add src/login.jsgit commit -m feat: add login validation# 切回main分支提交新内容制造完全一致的分叉git checkout mainecho // project base config  config.jsgit add config.jsgit commit -m chore: add base config# 切回特性分支执行Rebase操作目标基准是main分支git checkout feature/logingit rebase main 如果没有冲突Rebase会自动完成所有补丁的应用执行完成后执行git log --oneline --graph查看特性分支的提交历史输出如下 * 8d9e0f1 (HEAD - feature/login) feat: add login validation* 7c6d5b4 feat: add login core logic* 5a6b7c8 (main) chore: add base config* a7c3d90 chore: init project 可以看到特性分支的提交历史变成了完全线性的结构没有任何分叉原本的两个提交现在生成了两个全新的提交哈希值和之前完全不同它们的父节点变成了main分支的最新提交相当于把特性分支的开发“嫁接”到了main分支的最新提交之后,Rebase只能处理私有分支的历史整理和同步无法替代Merge在公共分支合并中的作用。

几乎每个开发者每天都在和Git打交道但分支合并时的灵魂拷问——“到底用Merge还是Rebase”却难倒了无数人。

合并完成后执行git log --oneline --graph查看历史输出如下 *   3f7e2d1 (HEAD - main) Merge branch feature/login|\| * 2c8d3f4 (feature/login) feat: add login validation| * 7d9e4f5 feat: add login core logic* | 5a6b7c8 chore: add base config|/* a7c3d90 chore: init project 可以清晰看到提交历史出现了分叉合并提交有两个父节点完整保留了两个分支的所有开发轨迹, 1.2 分支的本质 Git的分支本质上只是一个 指向某个提交对象的、轻量级的可变指针 ,Merge保留的是分叉的原始历史Rebase保留的是线性的修改历史两者都不会丢失代码内容,无论是GitHub PR、GitLab MR合并到公共主干时用Merge推荐--no-ff参数可以完整保留特性分支的开发历史保证主干分支的历史可追溯回滚方便且不会修改任何现有提交绝对安全。

5.1 不可逾越的核心红线Git官方明确警告 永远不要对已经推送到公共远程仓库、且有其他协作者基于其开发的提交执行Rebase操作 这是Git官方文档中明确标注的最高优先级警告没有任何例外, 此时我们只需要切回main分支执行快进合并就能把特性分支的内容合并进来不会生成任何合并提交main分支的历史也会保持完全线性 git checkout maingit merge feature/login 合并完成后main分支的提交历史如下 * 8d9e0f1 (HEAD - main, 这两个命令的核心区别到底是什么什么时候该用哪个怎么操作才能彻底避开坑本文将从Git底层对象模型出发用通俗的语言讲透Merge和Rebase的本质搭配可直接复现的实战示例明确区分易混淆点给出企业级可落地的最佳实践让你看完就能彻底搞懂再也不会用错, 2.1 Merge的两种核心合并模式 根据两个分支的提交状态Git Merge会自动选择两种合并模式Fast-Forward快进合并和Three-Way Merge三方合并, 需要完整保留开发轨迹的合规场景 很多企业有合规要求需要完整记录代码的开发、合并、修改全流程Merge的完整历史可以满足这个要求而Rebase会抹除原始开发轨迹,Git有reflog机制会记录所有分支指针的变化你可以通过git reflog找到原始提交的哈希值在30天内Git默认的过期时间都可以恢复, 四、Merge vs Rebase 核心差异全对比 我们用一张表格从核心维度彻底区分Merge和Rebase的区别所有内容均基于Git官方定义100%准确 对比维度Git MergeGit Rebase 提交历史修改 绝对不修改现有提交仅新增合并提交历史100%可追溯 重写现有提交生成全新的提交哈希原始提交会被废弃 提交结构 保留分支分叉历史合并后会出现分叉结构 彻底消除分叉合并后提交历史完全线性 冲突处理 仅需在生成合并提交时一次性解决所有冲突 逐个应用补丁时处理冲突有N个提交可能需要解决N次冲突 协作安全性 极高不会修改公共历史不会影响其他协作者 极低对公共分支执行会彻底破坏团队协作历史引发灾难性后果 可追溯性 极强合并提交的双父节点完整保留所有开发轨迹可精准追溯每个分支的合并过程 较弱原始分支的开发轨迹被抹除无法追溯提交的原始分支来源 回滚操作 简单合并提交可直接通过git revert -m 1 hash一键回滚整个特性分支的修改 复杂线性历史中无法直接区分特性分支的提交范围回滚需要逐个处理提交 学习门槛 低逻辑简单不易出错 高需要理解底层逻辑操作不当极易引发问题 核心优势 安全、完整、可追溯冲突处理简单 历史整洁、线性可读可灵活整理本地提交 五、核心红线与适用场景再也不会用错的决策标准 这是本文最核心的部分也是90%的开发者踩坑的根源, 误区6Merge的--squash参数和Rebase的fixup是一样的 纠正 git merge --squash是将特性分支的所有提交压缩成一个新的提交合并到目标分支不会保留特性分支的提交历史也不会生成双父节点的合并提交而Rebase的fixup是在变基的过程中将提交合并到上一个提交是在同一个分支内整理提交历史两者的使用场景和底层逻辑完全不同, feature/docs) docs: add quick start guide* a7c3d90 chore: init project 可以看到main分支的指针直接移动到了feature/docs分支的最新提交没有生成任何新的合并提交提交历史完全线性,此时用git rebase main可以把你的特性分支“嫁接”到主干的最新提交上保持提交历史线性避免生成无意义的合并提交, 二、Git Merge 深度解析完整保留历史的分支合并 Git Merge的官方定义是 将两个或多个开发历史合并在一起 ,唯一的补救方式是让所有协作者都删除本地的对应分支重新拉取远程的新分支这在多人协作的团队中成本极高极易引发代码丢失,它的核心设计目标是 重新设置分支的基准节点将分叉的提交历史线性化保持提交历史的整洁可读 , 三方合并的核心逻辑是Git会自动找到3个关键提交节点 待合并的两个分支的最新提交 main的HEAD和feature的HEAD 两个分支的最近共同祖先LCALowest Common Ancestor 基于这3个节点Git会对比两个分支相对于共同祖先的差异自动合并差异内容最终生成一个 全新的合并提交Merge Commit 。

3.4 Rebase的核心特性 提交历史完全线性化 彻底消除分支分叉提交历史是一条干净的直线可读性极强方便后续代码评审、日志排查 重写提交历史 会废弃原始提交生成全新的提交改变提交哈希值这是Rebase最大的风险点 无额外合并提交 不会生成Merge Commit避免了大量合并提交污染提交历史 原子性提交可保障 配合交互式变基可以整理零散的提交确保每个提交都是原子性的有明确的功能边界 3.5 Rebase的高频进阶用法交互式变基git rebase -i 交互式变基是Git提供的超强功能也是企业级开发中最常用的Rebase场景它可以让你在变基的过程中修改、合并、删除、重新排序提交彻底整理你的本地提交历史。

多人协作的共享分支合并 只要是多个开发者同时在开发的分支合并时必须用Merge禁止用Rebase避免重写公共历史, 整理本地私有分支的提交历史 开发过程中你可能会生成很多零散的提交比如“fix: typo”“wip: 临时提交”在提交PR/MR之前用交互式变基git rebase -i整理这些提交合并零散提交、修改提交信息、删除无用提交让每个提交都是原子性的、有明确意义的方便代码评审,。

当他们执行pull操作时Git会把两个不同的历史合并生成大量重复的提交最终导致整个仓库的提交历史彻底混乱甚至丢失代码, 你不需要盲目跟风用Rebase也不用无脑只用Merge只要理解了它们的底层逻辑守住了公共分支不用Rebase的核心红线根据场景选择合适的工具就能把Git用得得心应手再也不会因为合并操作踩坑, 误区5git pull的默认行为是Rebase 纠正 git pull的默认行为是git fetch git merge也就是先拉取远程分支的最新内容然后用Merge合并到本地分支, Rebase的核心逻辑拆解为5个不可拆分的步骤 Git自动找到当前分支与目标分支的 最近共同祖先LCA 提取当前分支中LCA之后的所有提交将每个提交的差异内容生成临时补丁文件 将当前分支的指针直接指向目标分支的最新提交也就是重新设置分支的“基准” 按照原始提交的顺序逐个将临时补丁应用到新的基准节点上每应用一个补丁就生成一个全新的提交 所有补丁应用完成后变基操作结束当前分支的提交历史变成完全线性的结构 这里的核心关键点也是和Merge最本质的区别 Rebase会重写提交历史LCA之后的所有原始提交都会被废弃生成全新的提交即使内容完全不变提交的哈希值也会完全改变 因为新提交的父节点、元数据都发生了变化,它的核心设计目标是在不修改现有提交历史的前提下完成两个分支的合并完整保留所有开发轨迹,这是理解Merge和Rebase核心区别的关键。

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:http://acg.inmoke.com/zixun/Jk/26361.html