git高阶知识: --rebase,变基的理解以及注意事项

发布时间:2026/7/26 6:03:43
git高阶知识: --rebase,变基的理解以及注意事项 变基(–rebase)介绍有时候你使用gitpush origin main:mainGit 拒绝你的推送正是因为远端仓库origin/main包含了你本地没有的新提交可能是你自己在另一台电脑提交的也可能是你的同事推送到远端的。Git 默认有一种保护机制它不允许你直接覆盖远端的历史。如果你强行推送别人的提交就会被“抹除”所以 Git 会报rejected并提示你先fetch拉取再推送。为了解决这个问题你确实需要使用git pull并且强烈推荐使用git pull --rebase变基。下面系统地讲解一下这背后的 Git 原理。一、 核心概念Fetch、Merge 和 Rebase要理解 pull首先要知道git pull的本质。git pullgit fetchgit merge或git rebasegit fetch抓取动作去远端仓库把最新的提交记录下载到本地但绝对不会修改你本地的代码和工作区。它只是更新了本地的“远端跟踪分支”如origin/main。git merge合并动作把下载下来的远端代码和你本地的代码“融合”在一起。如果修改了同一个文件的同一行就会发生冲突Conflict需要你手动解决。git rebase变基动作它的中文叫“变基”改变基底。它的逻辑是先把你本地的提交“暂时拔下来”藏好把远端最新的代码应用到你本地然后再把你“藏起来”的提交一个一个地重新“粘贴”重放到最新代码的后面。二、 原理图解Merge vs Rebase假设你和同事都在main分支上开发。你们共同的祖先是提交B。你本地在B之后提交了C。同事在远端B之后提交了D并推送到远端。此时你的本地和远端分叉了你的本地: A --- B --- C (你的提交) 远端仓库: A --- B --- D (同事的提交)1. 如果使用普通的git pull(默认使用 Merge)Git 会执行 fetch merge。它会创建一个新的“合并提交Merge Commit”E把 C 和 D 结合起来。合并后的本地: A --- B --- C --- E (Merge 提交) \ / --- D缺点提交历史变成了“网状”或“分叉状”。如果团队人多历史线会像蜘蛛网一样复杂充满无意义的Merge branch main of ...提交。2. 如果使用git pull --rebase(使用 Rebase)Git 会执行 fetch rebase。它先把你本地的C暂时拿掉。把远端的D拉到你的本地此时本地是 A-B-D。把你刚才拿掉的C重新“应用”到D的后面变成CC的变基版本。变基后的本地: A --- B --- D --- C优点提交历史变成了一条完美的直线非常干净别人看你的提交记录时就像是你看着同事的代码写完接着往下写的一样没有多余的分叉。三、 解决你当前报错的标准流程既然明白了原理你现在只需要按照以下步骤操作就能把代码推上去了第一步拉取远端代码并变基在终端输入gitpull--rebaseorigin main(注如果你当前就在 main 分支直接git pull --rebase也可以)第二步处理可能的情况执行完上面的命令后会有两种情况情况 A没有冲突顺利终端会提示Successfully rebased and updated refs/heads/main.这就说明变基成功了你的历史已经变成了一条直线。直接进行第三步。情况 B有冲突需要手动解决如果你和同事修改了同一个文件的同一行终端会提示CONFLICT (content): Merge conflict in...。不要慌按以下步骤处理打开代码编辑器如 VSCode找到冲突的文件手动决定保留谁的代码或者把两人的代码融合。解决完冲突后将修改后的文件添加到暂存区gitadd.# 或者 git add 冲突的文件名注意这里不要 commit而是告诉 Git 继续变基gitrebase--continue(如果冲突太复杂你想放弃这次变基可以输入git rebase --abort回到变基前的状态)第三步推送到远端无论是情况 A 还是情况 B 处理完毕现在你的本地代码已经包含了远端的最新内容并且你的提交在最顶端。直接推送即可gitpush origin main(因为你现在是顺着最新的时间线往下推的属于 fast-forward 快进推送Git 就会 happily accept 了)四、 避坑指南Rebase 的“黄金法则”(看不懂的话下面有详细的介绍)虽然rebase能让历史保持干净但它有一个绝对的红线新手一定要记住黄金法则永远不要在已经推送到公共分支且别人已经拉取过的提交上使用 rebase为什么因为 rebase 会改写历史把 C 变成 C’它们的 Hash 值不同了。如果你 rebase 了已经推送到远端的代码然后强行 push你同事本地的代码就会和远端对不上导致他们下次 pull 时出现极其混乱的冲突。正确用法✅本地分支 / 还没 push 的提交放心大胆地用rebase。✅拉取远端最新代码时使用git pull --rebase保持本地历史干净。❌公共的 main/master 分支上已经存在的历史提交绝对不要用git rebase去修改它们。一个小建议如果你希望以后每次git pull都默认使用 rebase可以修改一下 Git 的全局配置省去每次打--rebase的麻烦gitconfig--globalpull.rebasetrue希望这个讲解能帮你彻底理清 Git 的脉络以后遇到rejected你就知道这只是 Git 在温柔地提醒你“嘿有人改了代码你先拉取合并一下再推吧”关于第四、 避坑指南的详细说明:要理解Rebase 的“黄金法则”我们需要深入到 Git 的底层逻辑弄明白“改写历史”到底意味着什么以及它为什么会引发“灾难”。我们可以用一个通俗的比喻和具体的场景来拆解这个问题。一、 核心概念Git 的“指纹”Hash与“蝴蝶效应”在 Git 中每一次提交Commit都不是孤立存在的它包含两部分核心信息你这次修改的代码内容。它父提交的 Hash 值也就是前一个提交的“指纹”。因为当前提交包含了父提交的 Hash所以 Git 的提交链就像一条用指纹锁死的铁链。什么是 Rebase 改写历史当你使用 Rebase 时你改变了某个提交的“父节点”比如把它从 A 移到了 B 下面。因为父节点变了当前提交的 Hash 值就会彻底改变。更可怕的是“蝴蝶效应”当前提交的 Hash 变了它后面所有子提交的 Hash 值也会跟着全部改变结论Rebase 不是简单地“移动”代码而是销毁了旧的提交并创建了一连串全新的提交拥有全新的 Hash 指纹。二、 场景推演如果你违反了法则会发生什么假设你和同事小明都在main分支上协作。1. 初始状态风平浪静远端仓库有 3 个提交。你和同事小明都执行了git pull大家本地的代码和远端完全一致。远端 main: [1] --- [2] --- [3] 小明本地: [1] --- [2] --- [3] 你的本地: [1] --- [2] --- [3](方括号里的数字代表提交的 Hash 指纹)2. 你作死操作改写历史你发现提交[2]的注释写错了于是你在本地使用了git rebase -i修改了[2]。如前所述[2]变成了全新的[2]它后面的[3]也被迫变成了[3]。你的本地: [1] --- [2] --- [3] (指纹全变了)因为你修改了历史普通的git push会被拒绝。于是你使用了强制推送git push -f。远端 main: [1] --- [2] --- [3] (远端被你的新指纹覆盖了)3. 小明的灾难现场对不上号了此时小明本地的代码还是旧的[1] --- [2] --- [3]。他准备下班执行了git pull。此时 Git 的视角是怎样的Git 去远端拉取代码发现远端是[2]和[3]而小明本地是[2]和[3]。因为指纹完全不同Git 根本不认识它们Git 会认为“咦小明本地有[2]和[3]但远端有另外两个完全不相干的提交[2]和[3]。这一定是两个人分别写的两条平行线”于是Git 会尝试把远端的[2]和[3]合并Merge到小明的本地。结果就是代码重复如果[2]和[2]内容其实是一样的合并后小明的代码里会出现两份一模一样的代码疯狂冲突如果内容有微调Git 会把你修改过的[2]和小明本地的[2]放在一起对比瞬间爆发大量冲突。历史极其丑陋小明的提交历史会变成巨大的“X”型交叉原本一条直线变成了乱七八糟的网状。小明看着满屏的冲突和重复的代码一定会崩溃并跑来问你“你到底对 main 分支做了什么”三、 总结如何正确理解这条法则为了避免上述灾难Git 社区总结出了这条黄金法则。你可以用下面这个比喻来记忆本地的、还没 push 的提交你的私人草稿。你可以随意涂改、撕毁、重写随意 Rebase因为别人还没看到不影响任何人。已经 push 到公共分支且别人 pull 过的提交已经印刷出版的书籍。如果你发现书里有个错别字你不能把已经发到读者同事手里的书强行召回销毁Rebase 并 force push。你只能在下一版下一次正常的 Commit中写一个勘误表正常的提交去修复。正确的做法整理自己的草稿如果你本地有 3 个还没 push 的提交觉得太碎了可以用git rebase -i把它们合并成 1 个然后再 push。这是绝对安全且推荐的。同步别人的代码当你要拉取远端最新代码时使用git pull --rebase。这只是把你的“草稿”放到别人“已出版书籍”的后面没有修改别人的历史这也是绝对安全的。一句话口诀“Rebase 只能用来整理自己还没推上去的本地提交绝不能用来修改远端公共分支上已经存在的历史。”