一、问题背景
在日常协作开发中,我们经常会遇到这样的场景:
test分支是团队的公共测试分支- 我在自己的
feature-a分支上完成了开发 - 准备把
feature-a合并到test分支,推送给团队测试
按照常规流程,我执行了下面的操作:
bash
bash
git checkout test
git pull # 先拉取最新的 test
git merge feature-a # 合并自己的开发分支
合并过程中出现了一些冲突,我手动解决并提交后,准备 git push。但就在这时,问题出现了。
二、报错现象
再次执行 git pull 时,终端出现了下面的提示:
ruby
$ git st
位于分支 test
您的分支和 'origin/test' 出现了偏离,
并且分别有 2 和 1 处不同的提交。
(使用 "git pull" 来合并远程分支)
无文件要提交,干净的工作区
$ git pull
fatal: 无法快进,终止。
关键信息有两点:
- 本地分支和远程分支"偏离"了:本地有 2 个提交,远程有 1 个提交
git pull报错 :fatal: 无法快进,终止
三、问题分析
1. 为什么会"偏离"?
在我合并 feature-a 到本地 test 的这段时间里,有同事也往远程 test 推送了新的提交。于是历史变成了这样:
bash
本地: A---B---C---D---E (test, D、E 是我合并产生的提交)
\
远程: F (origin/test, 同事的新提交)
两边都有对方没有的提交,这就是 git 所说的"分叉"(diverged)。
2. 什么是"快进合并"(fast-forward)?
快进合并要求:当前分支必须是目标分支的祖先,也就是当前分支没有任何对方没有的新提交。这种情况下 git 只需要把分支指针往前推一下就行,不会产生新的 merge commit。
而我现在的情况是双方都有独立提交,无法快进,只能通过三方合并生成一个新的 merge commit:
css
A---B---C---D---E---M (test)
\ /
F-------/
3. 为什么 git pull 直接报错了?
报错信息 fatal: 无法快进,终止 说明 git 配置里存在:
ini
[pull]
ff = only
这个配置的意思是:git pull 时只允许快进式合并,遇到分叉就直接拒绝执行。这是一种比较严格的策略,目的是避免无意中产生 merge commit 把历史搞乱。
四、解决方案
方法一:用 git fetch + git merge 绕开限制(推荐)
bash
sql
git fetch origin
git merge origin/test
执行后,如果有冲突就手动解决,然后:
bash
sql
git add <冲突文件>
git commit
git push origin test
为什么这个方法能成功?
关键在于:pull.ff=only 只对 git pull 命令生效,对 git merge 命令不生效。
git fetch origin:只更新远程跟踪分支origin/test,不做任何合并,不受 pull 配置影响git merge origin/test:直接调用 merge 命令,默认允许创建 merge commit,会自动做三方合并
本质上 git pull 等价于 git fetch + git merge,只不过 git pull 多了一层 pull.ff=only 的"门禁"。手动拆开执行,就绕开了这个门禁。
方法二:修改 git 配置
如果不想每次都手动拆,也可以改全局配置:
bash
csharp
git config --global pull.ff true # 允许非快进合并(会自动创建 merge commit)
⚠️ 不推荐 对公共分支使用 pull.rebase=true,因为 rebase 会改写已经推送的历史,容易给协作带来麻烦。
五、解决冲突的标准流程
顺便记录一下手动解决冲突的标准动作:
1. 查看冲突文件
bash
lua
git status
2. 打开冲突文件,会看到这样的标记
markdown
<<<<<<< HEAD
我本地(包含 feature-a)的代码
=======
远程 origin/test 的代码
>>>>>>> origin/test
3. 手动编辑
保留需要的代码(通常是双方逻辑都要融合),然后删除 <<<<<<<、=======、>>>>>>> 这些标记行。
4. 标记冲突已解决并提交
bash
sql
git add <冲突文件>
git commit # 完成 merge commit
git push origin test
六、经验总结
- 理解
git pull的本质 :它就是git fetch + git merge的组合,搞清楚这一点,很多奇怪的报错都能迎刃而解。 pull.ff=only是一把双刃剑:它能避免无意中产生 merge commit,但在公共分支上反而经常成为障碍。要根据团队习惯选择合适的策略。- 公共分支用 merge,个人分支可用 rebase :对
test、develop、main这类公共分支,永远不要 rebase,因为这会改写已推送的历史,影响其他协作者。 - 合并前先 pull :在把 feature 分支合并到公共分支之前,先拉一次最新代码,可以大幅减少冲突的出现:
bash
bash
git checkout test
git pull
git merge feature-a
- 危险操作前先备份:遇到复杂的合并场景,先打个本地备份分支,万一搞乱了还能回退:
bash
git branch test-backup
- 冲突解决要谨慎 :不要无脑用
--ours或--theirs,大多数情况下需要手动融合双方代码,避免覆盖同事的工作。
七、一句话记忆
git pull因为pull.ff=only配置,遇到分叉就拒绝合并;而git merge命令本身没有这个限制,默认允许创建 merge commit,所以git fetch + git merge能顺利把分叉的两条历史合到一起。