Git 2.33.1 本地仅领先远程,pull 仍报无法快进
Git 2.33.1 中,本地仅领先远程时 pull --ff-only 可能误报无法快进。核对祖先关系,临时拆分 fetch 与 merge,并确认实际修复版本。
本地分支比远程多一个提交,远程没有新提交,执行 git pull --ff-only 却出现:
fatal: Not possible to fast-forward, aborting.
把操作拆成 git fetch 和 git merge --ff-only 后,合并又显示 Already up to date.。如果运行的是 Git 2.33.1,这组现象可能对应旧版 pull 的一处判断错误:它把“远程提交已经包含在本地历史中,无事可做”也当成了无法快进的失败。
本文只处理这种情况。若两边确实各有新提交,--ff-only 拒绝整合就是预期保护,不能靠升级把分叉变成快进。下面先核对实际提交关系,再决定是否按旧版问题处理。
本地领先,不代表需要一次反向快进
假设历史如下,字母只是示意提交:
A---B---C HEAD -> feature/demo
^
origin/feature/demo
本地已经包含远程的 B,还多了 C。从远程拉取时无需把本地分支退回 B,也无需重新生成 C。正常结果应当是“已经是最新状态”。
快进更新对应的是另一种关系:当前提交是目标提交的祖先,Git 可以直接把分支指针向前移动。旧版出错的地方在于,只检查了能否向前移动,没有正确放行目标本来就已包含在本地的情况。
这个判断错误有明确的版本边界:上游 Git 2.33.1 源码含有该逻辑;Git 2.34.0 已包含修复。不要把“升级到 2.33.2”当成可靠结论,上游 v2.33.2 标签下的相应判断仍没有这项修复。发行版或厂商可能回移补丁,最终还需结合实际软件包与运行结果判断。
这些都是历史版本号。需要升级时选择所在平台当前维护、包含修复的版本,不必为了排错专门安装已经老旧的 2.34.0。
先确认报错程序和拉取目标
在发生问题的同一终端、同一仓库查看:
git --version
git status
git branch --show-current
git config --show-origin --get-all pull.ff
配置输出为 only 时,说明该配置要求拉取只能快进。没有输出并不代表这次操作没有限制,命令行仍可能传入 --ff-only。保留配置来源信息有助于区分仓库配置与用户配置;这里不需要修改全局策略。
如果错误发生在编辑器的同步按钮中,还要在编辑器的 Git 日志里核对实际调用的 Git 路径、版本及命令。终端升级成功,不等于编辑器已经切换到同一个可执行文件。同步流程中的拉取失败,也不等于推送本身有故障。
下文假设当前分支是 feature/demo,要读取的是 origin 的同名分支。这两个名称都是示例,必须替换成原来失败命令的实际目标。若原命令是在功能分支上拉取 develop,就应比较 develop,不能换成同名分支后据此认定没有分叉。
先保存未提交工作和需要保留的未跟踪、被忽略文件,确认没有正在进行的 merge 或 rebase。后续的祖先判断本身不修改文件,但不要在不清楚仓库状态时继续执行合并。
用刚抓取的提交核对历史
暂停编辑器的自动抓取,然后执行:
git fetch origin feature/demo
只有抓取成功才继续。fetch 获取对象、更新相应引用及 FETCH_HEAD,不会把远程内容合入当前工作区。紧接着检查本地与这次抓取结果的差异:
git rev-list --left-right --count HEAD...FETCH_HEAD
git log --graph --oneline --decorate -n 20 HEAD FETCH_HEAD
git merge-base --is-ancestor FETCH_HEAD HEAD
第一条输出的左列是本地独有的提交数,右列是抓取目标独有的提交数。例如 1 0 表示本地仅领先一个提交;0 0 表示两端相同。两列都大于零,说明双方都有对方没有的提交,应停止按本文的“本地仅领先”场景处理。
第三条检查目标是否已经是本地的祖先,成功时通常没有文字输出,退出状态为 0;1 表示不是祖先,其他非零状态表示命令出错。应查看所用终端的退出状态,不能把任何“没有输出”都解释成成功。日志只展示最近 20 条,祖先检查不依赖这 20 条是否足够画完整张图。
这些判断必须基于同一轮抓取。另一条 fetch 或编辑器的后台抓取可能改写 FETCH_HEAD;发生这种情况就重新执行抓取与检查,不要沿用旧结论。浅克隆、缺失对象或无效引用导致检查出错时,也不能当成旧版缺陷的证据。
暂时把拉取拆成抓取和合并
已经确认抓取成功、目标是本地祖先、工作状态已保存,而且中间没有新的抓取时,可以执行:
git merge --ff-only FETCH_HEAD
在上面“本地仅领先”的场景中,这一步应显示 Already up to date.,不会创建合并提交,也不会把本地领先的提交丢掉。它绕开的是旧版 pull 的前置判断,仍然保留只能快进的限制。
若它也拒绝合并,或输出的行为与刚才的祖先检查不一致,就应停下来重新核对当前分支、抓取目标和引用变化。不要顺手改成 rebase、添加允许无关历史的选项,或用硬重置把本地提交抹掉。文件内容看起来一样,也不能代替提交祖先关系。
这条临时路径没有修改 Git 配置,之后可以继续保留 pull.ff=only。它也不负责推送;在需要把本地提交交给远程时,仍按仓库原有的审核与推送流程处理。
升级后的验证和回退边界
安装平台维护的修复版本后,重新打开相关终端或编辑器,确认报错位置实际使用的版本已更新。远程没有变化、当前分支和目标仍与前面一致时,再运行:
git pull --ff-only origin feature/demo
git status
git log -1 --oneline
预期是拉取正常结束,本地领先提交仍在,工作区没有意外变化。如果远程在验证期间新增了提交,应重新判断两边历史;此时再次出现无法快进,不能据此断定升级无效。
在已确认目标是祖先的前提下,临时的 merge --ff-only 是无操作,不需要用重置命令“撤销修复”。若没有遵守前面的条件,实际发生了快进或其他状态变化,就先检查日志和引用,不要把本文的无操作回退边界套上去。
升级工具本身不需要删除仓库或清除 .git。如因平台兼容性必须恢复原安装包,可以继续使用拆开的抓取与合并流程;保留原有项目副本和本地提交,方便核对升级前后的状态。


