
上周有个同事在群里发了一句rebase 到一半冲突解决了结果 rebase 一直停在第一步怎么弄都过不去。当时我隔着屏幕都能感受到他的崩溃——他已经把冲突文件全改好了git status 看着也干净可一回车还是那句“Rebasing (1/3)”rebase 直接僵死。最后他实在没办法只能 reset 回原位重新来等于白干。这种场景我在实际开发里见得太多了。很多人用 VSCode 处理 git rebase 冲突习惯性地改完文件就以为完事了压根没意识到改完文件只是第一步你还得让 git 知道你“接受”了这个冲突的最终结果。如果你也被 VSCode 里那堆色彩块整得晕头转向改完冲突却永远无法继续 rebase那么这篇文章会从头到尾帮你把这件事捋清楚。先交代下我自己的情况方便你对号入座。我平时主力 Windows VSCodegit 操作大部分在集成终端里完成但冲突解决这一步我基本不靠命令行敲git add一个个来而是在 VSCode 的合并编辑器里点按钮、挑代码块、最后同步一下状态。这套流程已经用了三年多Rebase 和 merge 的冲突处理都覆盖到了。下面写的全是实际踩坑换来的经验不是文档搬运。1. 先搞清楚rebase 的冲突到底卡在哪一步1.1 rebase 的工作原理一句话版很多人不理解 rebase 为什么容易把仓库搞得“很乱”其实是没搞懂它的运行机制。rebase 的核心动作是把当前分支上从分叉点开始的所有提交“摘下来”然后以目标分支最新的提交为新的基点把摘下来的提交一个个重新摆上去。摆的过程其实就是重新生成提交每一个提交在被重新应用时git 都会尝试把补丁打到新的代码上。如果打补丁的过程中两边的代码在同一个位置都发生了改动git 无法判断到底用谁的内容就会停下来标记为冲突状态等着你人工决策。这个状态就是Rebasing提示符出现的原因。这里有个新手最容易漏掉的关键点rebase 冲突是一个“多轮”过程不是一轮解决就结束了。如果当前分支上有 3 个待变基的提交第 1 个提交冲突你解决了git 会继续应用第 2 个、第 3 个。所以你会看到Rebasing (1/3)然后(2/3)再到(3/3)。很多人卡住是因为只处理了其中一轮却没注意到提示已经跳到了下一个数字。1.2 冲突状态和 merge 冲突的区别同样是冲突git merge和git rebase给你的现场是完全不同的。merge 冲突发生后仓库处于MERGING状态冲突标记集中在单个合并提交里。你解决完冲突后所有修改会合成一个最终的 merge commit。而 rebase 冲突发生时仓库处于REBASING状态git 会把当前进度记录在.git/rebase-merge或.git/rebase-apply目录里。你的每次解决方案都服务于“即将生成的某一个提交”不是一次性生成一个大的合并提交。理解了这一点你就知道为什么 rebase 冲突解决后“还需要继续”了。因为你解决的是第 N 个提交的冲突解决完、暂存、然后git rebase --continuegit 才会去生成这个提交然后立刻处理下一个。整个过程像流水线作业一环接一环。注意git rebase --continue不是可选项而是必须做的“提交确认动作”。只改文件、不暂存、不 continuerebase 永远停在原地。2. VSCode 里解决 rebase 冲突前值得先做好的准备2.1 确认你的 VSCode 版本和 git 版本VSCode 的合并编辑器在 1.68 版本之后做了大幅升级现在的冲突界面是三栏式左侧是“当前更改本分支”右侧是“传入更改被变基到的分支”中间是“结果编辑器”。你直接在中间结果区改代码顶部还能逐个块操作“接受当前更改”“接受传入更改”“接受两者更改”。如果你的 VSCode 版本太老可能看不到这些便捷按钮而是朴素的拆分视图。建议升级到最新稳定版这能让你在冲突解决上省至少一半时间。最新版还额外支持“将所有冲突统统按某一边解决”的批量操作处理大规模冲突尤其提速。git 版本方面尽量别低于 2.30。旧版本在 rebase 冲突场景下界面提示和状态信息都不够清晰。Windows 下建议直接安装 Git for Windows 最新版然后让 VSCode 的git.path指向它的git.exe避免有些自动识别的路径导致奇怪问题。2.2 设置用户级别的 diff 与 merge 工具VSCode 不仅能看冲突还能直接把 git 的 merge 工具指定成它自己。在settings.json里加这么一段{ git.enableSmartCommit: true, git.confirmSync: false, git.autorefresh: true, git.mergeEditor: true }重点说下git.mergeEditor。这是 VSCode 自带的可视化合并编辑器打开冲突文件后它会默认以合并对比模式呈现而不是普通文本编辑。配合顶部按钮“接受当前”“接受传入”“在冲突之间导航”我觉得这是目前所有编辑器里最适合解决 git 冲突的方案之一。如果你更习惯用外部工具也可以在配置里声明{ git.mergeTool: code, git.mergeToolArgs: [--merge, $LOCAL, $REMOTE, $BASE, $MERGED] }不过实测下来内置 smerge editor 就已经够用了外部工具反而多一层环境变量配置成本新手不建议折腾。2.3 开启源代码管理面板的自动刷新rebase 冲突过程中文件会不断在“冲突中”“已暂存”“已修改”状态之间跳来跳去。VSCode 默认会自动刷新源码管理视图但有些情况下尤其是文件特别多时自动刷新会滞后。别怕麻烦在源码管理视图右上角点一下“刷新”按钮或者干脆设置git.autorefresh为true确保状态变化能第一时间反映出来。这个细节看起来不起眼但它能避免你产生“我明明解决了为什么 git status 还显示冲突”的错觉。很多时候并不是没解决而是界面没刷新。3. 实战在 VSCode 里完整解决一次 rebase 冲突3.1 上手前先做一个模拟场景为了写这篇内容我特意构造了一个小仓库来做演示。仓库初始就一个README.md然后我从main分支分出feature分支两边各自修改了 README 的同一行。接着执行git checkout feature git rebase main执行后终端立刻显示Auto-merging README.md CONFLICT (content): Merge conflict in README.md error: could not apply a1b2c3d... add feature doc hint: Resolve all conflicts manually, mark them as resolved with git add/rm conflicted files hint: and then run git rebase --continue.看到这个信息后我打开 VSCode源码管理面板里README.md会出现在“更改”列表中冲突文件前方通常会有一个明显的感叹号图标。单击这个文件VSCode 会自动进入合并编辑器。3.2 合并编辑器里的操作细节三栏布局你需要注意这几点左侧“当前更改”是你在 rebase 之前的提交内容也就是 feature 分支当时写的内容。右侧“传入更改”是你在 rebase 目标分支main上提交的内容它会覆盖到当前分支之上。中间“结果编辑器”是最终写入文件的版本。每个冲突区块上方都有操作按钮。比如“接受当前更改”“接受传入更改”“接受两者更改”顺序拼接/全面合并。“接受两者更改”的实际行为是先把当前更改内容写进去再接一段传入更改的内容中间没有自动换行或分隔符处理要注意合并后的语法是否完整。逐个块操作完成后点击右上角的“完成合并”按钮。此时 VSCode 并不会自动替你执行git add它只是关闭合并编辑器自动将文件标记为已解决并暂存。实测在最新版本中点“完成合并”后文件会自动进入“已暂存更改”区所以这一步其实也算替你做了暂存动作。这里必须强调一点只改文件、不点“完成合并”、不手动暂存的话rebase 根本无法进入下一步。我见过太多人直接在冲突文件里改来改去改完就回头执行git rebase --continue结果 git 说你还有未暂存的修改。这其实就是“无法继续 rebase”原因里最常见的一种。3.3 处理完所有后手动执行 continue回到集成终端执行git rebase --continue如果当前是第一个提交的冲突rebase 会生成第一个提交接着尝试应用第二个提交。如果第二个也冲突会再次进入冲突状态你需要重复上面的流程。全部提交都应用完毕后终端会提示类似Successfully rebased and updated refs/heads/feature.到这一步rebase 才算真正走完。有一类特殊场景虽然发生了冲突但你最终发现传递进来的目标分支内容已经包含了你要改的东西那当前这个提交就没必要保留了。这时候有两种做法手动把所有冲突都协商成“接受传入更改”然后 continue生成一个等效的空提交不推荐历史里会多个冗余提交。更专业的做法是执行git rebase --skip让 git 跳过当前这个提交不把它放到新历史里。--skip不是用来逃课解决冲突的它适用于“这个提交在变基后已经不需要了”的情况。用之前一定确认清楚跳过的提交会彻底从当前分支历史里消失。4. 无法继续 rebase 的典型原因与排查流程4.1 最常见的五种“卡死”现场我把实际工作中遇到的“rebase 无法继续”的情况做个汇总下面这张表基本覆盖了 90% 的场景症状根本原因解决方案git rebase --continue提示 no changes文件改动没有暂存或改动内容与冲突前完全一致先git status确认暂存区必要时git add后重试continue 后弹出 commit message 编辑器这是正常行为不是错误保存信息后提交完成若不需要修改可直接关闭提示Untracked files would be overwritten冲突解决时生成了新文件且未加入暂存确认后git add新文件或删除无效文件后再 continuecontinue 后立刻又回到冲突分支上有多个待应用提交逐个处理重复解决直到出现 success 提示一直停在某个(N/M)数字不动N 过了但 commit message 编辑器未关闭退出编辑器并保存或设置core.editor为非交互模式错误地执行了git commit而不是 continue操作语义混淆在 rebase 状态下只使用 continue/skip/abort不要手动 commit第二行的“改动内容与冲突前完全一致”是特别隐蔽的一个坑。比如你处理冲突时把结果改成了和传入分支一模一样git 会判定当前提交没有产生任何新变化continue 时它不允许生成空的提交这时会拒绝继续并提示“nothing to commit”。这种情况的应对方式有两种如果你确实认为该提交已不需要直接git rebase --skip。如果你想让这个提交保留一个空变动比如保留提交说明设置git config rebase.autoSquash之类的不太管用需要允许空提交git commit --allow-empty也不行因为 continue 不接受这种单独 commit 行为。最稳妥的做法还是 skip 或 reset 后重新规划提交。4.2 从终端判断当前到底处于什么阶段很多人卡住是因为不知道 rebase 当前到哪一步了。有三个命令值得记住git status git rebase --show-current-patch cat .git/rebase-merge/done # 或 .git/rebase-apply/donegit status是最直观的在 rebase 期间它会在最上方显示rebase in progress; onto commit字样并列出“当前已解决/未解决”的文件。git rebase --show-current-patch会显示当前被应用的补丁内容帮你快速回忆自己正处理的是哪个提交。done文件记录着已经处理完的提交编号和完整哈希适合确认进度。实操中我很少看done基本都是 status show-current-patch 组合这两个足够定位 95% 的问题了。4.3 遇到 rebase 彻底僵死的兜底方案如果你尝试了各种 continue、skip 都没有头绪或者改动太乱理不清别硬刚直接回滚重来完全可行git rebase --abort执行后分支会完美回到 rebase 之前的状态所有冲突过程中的改动全部丢弃。你之前对冲突文件做的修改也一并消失但只要还没执行过--skip这些修改本来就不属于你的正常开发成果丢掉也不心疼。说实话我建议所有人在大规模 rebase 之前先做个保险操作git branch backup-before-rebase一条命令多一个完全相同的备份指针。rebase 失败、冲突太多、历史弄乱随时可以用 backup 分支捞回来不用提心吊胆去git reflog里翻。5. 在 VSCode 的可视化操作中绕开那些坑5.1 源码管理面板的“暂存全部更改”按钮别乱点rebase 中途源码管理面板会同时显示三类东西已暂存的更改、已解决的冲突文件、可能出现的未跟踪文件。不少人图省事直接点“暂存全部更改”以为把文件都 add 一遍就干净了。这么做有个隐患如果你在 rebase 期间临时多出了不该提交的文件比如编译产物、调试日志、临时配置文件点“全部暂存”会把它们一起带进 continue 生成的提交里。轻则污染提交内容重则后续还得重新整理历史非常不划算。正确姿势是逐个检查冲突文件确认无误后只暂存必须暂存的那几个。如果你用了合并编辑器记得完成后主动 CtrlS 保存再点完成合并避免编辑器没有自动保存导致的“文件倒回”现象。5.2 处理完冲突别急着 continue先搜一遍冲突标记我有个坚持了两年的习惯每次在 VSCode 里解决完冲突后顺手用CtrlShiftF全局搜索这几个字符串 不要嫌多余。合并编辑器虽然会把检出的冲突块都标出来但偶尔会出现嵌套冲突、多行标记残留、或者你手动修改结果时不小心把分隔符带进来的情况。全局搜索一遍确认没有残留的冲突标记再执行 continue基本能避免“解决了又报错”的尴尬循环。尤其是一些改动范围大的合并左边改了一堆、右边也改了一堆VSCode 的合并编辑器会把每个冲突块展示出来但个别块如果内容完全一致它可能直接自动合并而不产生标记。这时全局搜索依然干净放心。5.3 git 集成终端的路径问题Windows 上如果 VSCode 集成终端检测不到 git会报“无法将 git 项识别为 cmdlet、函数、脚本文件或可运行程序的名称”这类错误。这个问题虽然不直接属于 rebase 冲突本身但不解决的话连 rebase 命令都跑不了。处理办法打开 VSCode 设置搜索git.path填入你 Git for Windows 的安装路径。默认通常在C:\Program Files\Git\bin\git.exe填完重启 VSCode终端里的git命令就能被正确识别了。如果你用了 Windows 自带的 WSL记得在 WSL 里也给自己的发行版装 git并且 VSCode 的远程开发扩展连接到 WSL 后git.path要指向 WSL 内部的 git 路径因为 WSL 里的文件系统访问方式不一样讲起来又是一篇长文这里先点到为止。6. 从操作细节到心理建设rebase 冲突处理的一些心得6.1 理解“冲突不是灾难是提示”用 Git 这些年我最大的感受是很多人对冲突有一种本能的恐惧觉得冲突发生说明两个人写代码“吵起来了”仓库要炸了。其实完全不是这样。冲突只是 git 在没法替你决定“到底该选哪边的改动”时把问题交还给你而已。它恰恰说明 git 是诚实且谨慎的用冲突提示来换取你的知情权和决策权。处理 rebase 冲突的心态应该是把每次冲突当成一次代码 review 机会。你被迫去读一遍别人最近的改动再回顾一下自己之前写的逻辑想清楚两边的改动如何在同一个文件里共存。这种审视往往能提前发现不少代码间的隐性耦合问题。6.2 可视化合并编辑器的一个隐藏技巧VSCode 的合并编辑器里除了“接受当前”“接受传入”之外还有一个不太被注意但很实用的能力你可以在结果编辑器里直接自由修改文本。这意味着你完全不必局限于“二选一”而是可以手工拼出第三版本的最终代码。比如一个人改了函数签名另一个人改了函数体内部逻辑两者互不兼容时你就可以在中间结果区手工调整成兼容的新版本。这种方式比“接受两者更改”再手动修效率高多了。我建议进阶用户都试试把手放键盘上快捷键操作CtrlAlt方向键可以在冲突块间跳转Enter可以直接快速接受当前块。具体快捷键会随版本略有差异但你可以在键盘快捷方式里搜accept、conflict自定义不用死记。6.3 习惯性保留一个“还能救”的备份写代码这些年看到太多因为 rebase 失误导致工作丢失的案例了。rebase 本身是重写历史的行为做之前一定要备份。就算你觉得“我自己历史只有两个提交不会有事”也请养成下面这个肌肉记忆git checkout your-branch git branch your-branch-backup git rebase main万一中途发现 rebase 思路完全错了、目标分支根本不是你想变基的那条直接利用备份分支一键回到之前状态不用卑微地去 reflog 翻找碎片。备份分支用完删掉即可git branch -D your-branch-backup6.4 关于交互式 rebase 的一个关联提醒本文说的主要是一般 rebase但实际开发中还有一种很常见的操作叫git rebase -i交互式 rebase可以用来整理提交历史比如squash、reword、drop。交互式 rebase 也会进入相同的REBASING状态冲突处理逻辑完全一致。如果你在处理交互式 rebase 时遇到冲突上述所有的继续、跳过、放弃策略全部适用。唯一要额外留意的是交互式 rebase 的--continue有时会打开编辑器让你为 squash 或 reword 后的新提交重新编写说明。如果不想编辑器一直弹出来可以把默认编辑器换成不废话的类型比如git config --global core.editor code --wait这样 continue 时会自动打开 VSCode 窗口让你编辑提交信息保存关窗后自动继续。比在终端里按 vim 顺手很多。7. 常见问题速查表与最终建议7.1 把问题整理成一张表收藏备用问题判定方法一句话解法continue 后无法继续git status显示未暂存修改git add对应文件后重试没有任何修改却提示冲突已解决git diff为空确认是否要继续不要的直接--skip卡在某一步怎么都过不去git rebase --show-current-patch看当前提交解决当前冲突后 continue文件被 mark 为 deleted 或 added源码管理面板看文件状态执行git rm或git add后继续想放弃所有操作重来确认当前处于 rebase 状态git rebase --abort忘了 rebase 前代码长什么样看备份分支git diff your-branch-backup..your-branch7.2 我个人的几个习惯最后分享几个我经过反复实践固定下来的处理规矩第一每次 rebase 前一定用git status看一遍当前是否有未提交的更改。如果有先git stash或 commit 掉避免和 rebase 的冲突逻辑互相干扰。这一步能挡掉一大部分“我会不会把工作搞丢”的恐慌。第二在 VSCode 里尽量全程通过源码管理面板观察状态变化而不是只在终端里苦等。面板会把“哪个文件冲突了、哪个已暂存、哪个还没处理”列得非常清楚比在命令行里敲git status更直观。第三冲突解决完毕后的第一次 continue 之前我会强制自己再检查一次当前分支名和 rebase 目标。git status第一行会显示On branch feature第二行往下会显示You are currently rebasing branch feature on main。这两行信息15秒就能确认完却能防止你因为前一晚熬夜导致的分支混淆。7.3 别怕 rebase它只是另一种工具写到这里你会发现 VSCode 里处理 git rebase 分支冲突本质上不是靠什么高深技巧而是“知道 rebase 的阶段是什么、冲突改动如何暂存、continue 如何触发下一轮”。把这三个问题弄明白搭配好可视化合并界面你完全可以摆脱对 rebase 的恐惧。我常年推荐身边同事的做法一直是该用 merge 用 merge该用 rebase 用 rebase没有孰优孰劣只有适合场景。但如果你正在用 rebase 整理历史、同步主干那么上述这套 VSCode 处理冲突的流程一定用得上。下次再遇到Rebasing (2/5)别头大打开编辑器按块解决暂存continue一轮轮走完新的线性历史就在眼前了。