Git Push 被拒绝:从 non-fast-forward 到 MERGING 状态的完整解决过程

在使用 Git 向远程仓库提交代码时,我遇到了一个比较典型的问题:
git.exe push --progress "origin" master:master
执行后,Git 提示:
To https://gitee.com/lhang0407/aperbio-erp.git
! [rejected]        master -> master (non-fast-forward)
error: failed to push some refs to 'https://gitee.com/lhang0407/aperbio-erp.git'

hint: Updates were rejected because the tip of your current branch is behind
hint: its remote counterpart. If you want to integrate the remote changes,
hint: use 'git pull' before pushing again.
从提示来看,是本地分支落后于远程分支,需要先执行 git pull,将远程修改合并到本地,然后才能继续推送。 但在实际处理过程中,我又遇到了 Git 处于 MERGING 状态、无法继续执行 pull --rebase 的问题。 这篇文章完整记录一下问题产生的原因、Git 当前处于什么状态,以及最终为什么只需要执行:
git commit -m "Merge origin/master into master"
git push origin master
就可以解决问题。

一、最开始的问题:non-fast-forward

最初执行推送命令:
git push origin master
Git 返回:
! [rejected] master -> master (non-fast-forward)
non-fast-forward 可以理解为:
远程仓库的提交记录已经向前推进,而本地仓库还停留在较早的位置。
假设最开始本地和远程都是同一个提交:
A
之后,本地新增了一个提交 B
A --- B
与此同时,远程仓库也被其他人或者其他设备提交了一个新版本 C
A --- C
此时,本地和远程就产生了分叉:
      B    本地 master
     /
A
     
      C    远程 origin/master
本地有一个远程没有的提交 B,远程也有一个本地没有的提交 C。 这时候直接执行:
git push origin master
相当于要求远程仓库从 C 改成 B。 但这样可能会丢失远程的 C 提交,因此 Git 默认拒绝推送,并提示:
non-fast-forward
这其实是一种保护机制,目的是防止远程代码被意外覆盖。

二、为什么不能直接强制推送

看到推送失败后,有些人可能会直接使用:
git push --force origin master
虽然强制推送可能让命令执行成功,但它有可能覆盖远程仓库中的提交。 在上面的例子中,远程的提交是:
A --- C
本地是:
A --- B
强制推送后,远程可能直接变成:
A --- B
这样远程提交 C 就可能从当前分支历史中消失。 因此,只要远程的修改还有保留价值,就不应该随便使用:
git push --force
正确思路应该是:
  1. 获取远程修改;
  2. 将本地和远程修改合并;
  3. 完成合并提交;
  4. 再推送到远程。

三、执行 Pull 后进入 MERGING 状态

我在 TortoiseGit 中执行了拉取或合并操作后,再次进入 Git Bash,发现命令行显示:
(master|MERGING)
执行:
git status
得到:
On branch master
Your branch and 'origin/master' have diverged,
and have 1 and 1 different commits each, respectively.

All conflicts fixed but you are still merging.
  (use "git commit" to conclude merge)
这里包含两个重要信息。

1. 本地和远程各自有一个不同的提交

Your branch and 'origin/master' have diverged,
and have 1 and 1 different commits each
意思是:
  • 本地 master 有一个远程没有的提交;
  • 远程 origin/master 也有一个本地没有的提交;
  • 两个分支已经分叉。
也就是类似下面的结构:
      本地提交 B
     /
A
     
      远程提交 C

2. 冲突已经处理完,但合并还没有正式结束

All conflicts fixed but you are still merging.
意思是:
  • Git 已经开始执行一次合并;
  • 合并过程中产生的冲突已经解决;
  • 冲突文件也已经加入暂存区;
  • 但是还没有创建最终的合并提交。
此时 Git 仓库正处于一个中间状态:
master|MERGING
Git 已经准备好了合并结果,但还需要执行一次 git commit,才能正式结束合并过程。

四、为什么 git pull –rebase 无法执行

看到远程分支有更新后,我尝试执行:
git pull --rebase origin master
结果 Git 提示:
error: You have not concluded your merge (MERGE_HEAD exists).
hint: Please, commit your changes before merging.
fatal: Exiting because of unfinished merge.
这句话的核心是:
You have not concluded your merge
也就是:
当前已经有一个合并操作正在进行,但是还没有完成。
Git 在执行合并时,会在 .git 目录中记录一个叫作 MERGE_HEAD 的状态文件。 只要 MERGE_HEAD 还存在,就说明当前仓库处于未完成的合并状态。 此时不能再启动另一次:
  • git pull
  • git merge
  • git pull --rebase
  • git rebase
因为 Git 不允许在一次合并尚未结束时,再开始另一套合并或变基操作。 这就像一个事务已经执行到一半,必须先选择:
  • 提交当前事务;
  • 或撤销当前事务。
不能在旧事务还没有结束时,再启动一个新事务。

五、为什么此时 git push 仍然会失败

在没有完成合并提交之前,我又执行了:
git push origin master
仍然提示:
! [rejected] master -> master (non-fast-forward)
这是因为虽然远程代码已经被拉取下来,冲突也已经解决,但合并结果还没有形成一个正式提交。 此时 Git 的工作区可能已经包含了本地和远程的最终代码,但提交历史还是没有真正连接起来。 在完成合并前,提交历史仍然类似:
      B    本地 master
     /
A
     
      C    远程 origin/master
只有执行合并提交后,Git 才会创建一个新的提交,例如 M
      B -------
     /          
A                M
               /
      C --------
这个 M 就是合并提交。 它同时包含两个父提交:
  • 本地提交 B
  • 远程提交 C
创建 M 以后,本地分支就同时包含了本地修改和远程修改。 此时再向远程推送,就不会覆盖远程已有的 C,因此推送可以正常完成。

六、最终解决方法

由于 git status 已经明确提示:
All conflicts fixed but you are still merging.
(use "git commit" to conclude merge)
所以只需要先完成合并提交:
git commit -m "Merge origin/master into master"
然后再执行推送:
git push origin master
完整命令如下:
git commit -m "Merge origin/master into master"
git push origin master
执行成功后,命令行中的状态会从:
(master|MERGING)
恢复为:
(master)
这表示合并状态已经结束。 此时 Git 的提交历史大致如下:
*   M Merge origin/master into master
|
| * C 远程仓库原来的提交
* | B 本地仓库原来的提交
|/
*   A 双方共同的历史提交
最后将合并提交 M 推送到远程仓库,整个问题就解决了。

七、整个处理流程的来龙去脉

这次问题的完整过程可以概括为:
flowchart TD
    A[本地和远程原本一致] --> B[本地新增一个提交]
    A --> C[远程新增一个提交]
    B --> D[本地与远程分叉]
    C --> D
    D --> E[执行 git push]
    E --> F[提示 non-fast-forward]
    F --> G[拉取并合并远程代码]
    G --> H[处理代码冲突]
    H --> I[冲突已解决但仍处于 MERGING]
    I --> J[执行 git commit 完成合并]
    J --> K[生成 Merge Commit]
    K --> L[执行 git push]
    L --> M[推送成功]
实际命令流程大致是:
# 查看当前状态
git status

# 拉取远程修改并执行合并
git pull origin master

# 如果发生冲突,手动处理冲突文件
# 然后将处理后的文件加入暂存区
git add .

# 完成合并提交
git commit -m "Merge origin/master into master"

# 推送最终合并结果
git push origin master
在我的实际情况中,冲突已经通过 TortoiseGit 解决并加入暂存区,因此不需要再次执行:
git add .
只需要执行:
git commit -m "Merge origin/master into master"
git push origin master
即可完成。

八、MERGING 状态下有哪些选择

进入:
master|MERGING
后,一般有两种处理方向。

方案一:继续完成当前合并

如果冲突已经正确解决,希望保留本地和远程双方的修改,就执行:
git status
git add .
git commit -m "Merge origin/master into master"
git push origin master
如果 git status 已经提示:
All conflicts fixed
并且所有文件已经暂存,那么可以直接执行:
git commit
或者:
git commit -m "Merge origin/master into master"
这是本次问题采用的方案。

方案二:放弃当前合并

如果发现合并方向不对、冲突解决错误,或者想重新处理,可以执行:
git merge --abort
这会尝试将仓库恢复到开始合并之前的状态。 然后可以重新选择其他处理方式,例如:
git pull --rebase origin master
但是,如果冲突已经全部处理完成,通常没有必要再放弃合并重新操作。

九、Merge 和 Rebase 的区别

处理本地与远程分叉时,常见的两种方法是:
  • Merge;
  • Rebase。

Merge

执行:
git pull origin master
默认情况下通常会执行类似:
git fetch origin
git merge origin/master
如果本地和远程都有新提交,Git 会创建一个合并提交:
      B -------
     /          
A                M
               /
      C --------
优点:
  • 保留真实的分支合并历史;
  • 不会修改已有提交;
  • 对已经共享的分支相对安全。
缺点:
  • 可能出现较多合并提交;
  • 提交历史看起来不够线性。

Rebase

执行:
git pull --rebase origin master
Git 会将本地提交暂时取下,先更新到远程最新位置,再把本地提交重新应用到后面:
A --- C --- B'
这里的 B' 是重新生成的本地提交,它和原来的 B 内容可能相同,但提交哈希已经不同。 优点:
  • 提交历史更加整洁;
  • 历史是一条直线;
  • 减少不必要的合并提交。
缺点:
  • 会改写提交历史;
  • 如果使用不当,可能给团队协作带来问题;
  • 已经进入 MERGING 状态后,不能直接切换到 Rebase,必须先完成或取消当前合并。
在这次问题中,Git 已经处于普通 Merge 流程,因此正确做法是先完成当前合并,而不是再执行:
git pull --rebase

十、为什么 TortoiseGit 中可能看不到 Pull 菜单

这次操作中还有一个现象:在 TortoiseGit 的右键菜单里没有直接看到 Git Pull。 这种情况可能有几个原因。

1. Pull 被放到了 Sync 窗口

部分版本或菜单配置中,拉取操作可能集中在:
右键项目目录
→ TortoiseGit
→ Sync
在 Sync 窗口中,可以看到:
  • Pull;
  • Push;
  • Fetch;
  • 远程与本地提交信息。

2. Windows 11 折叠了右键菜单

Windows 11 默认会简化右键菜单,可以点击:
显示更多选项
或者使用:
Shift + F10
打开传统右键菜单。

3. 当前仓库已经处于 MERGING 状态

如果仓库已经在执行合并,继续 Pull 并不是正确操作。 此时真正应该做的是先查看:
git status
如果看到:
All conflicts fixed but you are still merging.
就应该执行:
git commit
而不是继续寻找 Pull 菜单。

4. 右键位置不是 Git 工作目录

TortoiseGit 的仓库菜单通常需要在 Git 项目目录中打开。 可以确认当前目录中是否存在:
.git
如果不在 Git 仓库目录中,部分菜单可能不会显示。

十一、排查 Git 问题时,git status 最重要

这次问题中最关键的命令并不是:
git pull
也不是:
git push
而是:
git status
它明确告诉了我们:
All conflicts fixed but you are still merging.
(use "git commit" to conclude merge)
Git 已经直接给出了下一步操作:
git commit
很多 Git 问题看起来比较复杂,实际上只要先执行:
git status
通常都能判断仓库当前处于什么状态,例如:
  • 正常状态;
  • 有未提交文件;
  • 有未暂存文件;
  • 正在合并;
  • 正在 Rebase;
  • 存在冲突;
  • 本地领先远程;
  • 本地落后远程;
  • 本地与远程已经分叉。
因此,遇到 Git 异常时,建议首先执行:
git status
然后根据状态提示继续操作,而不是连续尝试多个 pullpushrebase 命令。

十二、常见错误操作

1. MERGING 状态下继续 Pull

错误示例:
git pull --rebase origin master
如果当前合并还没有结束,Git 会提示:
You have not concluded your merge
正确做法是二选一:
git commit
或者:
git merge --abort

2. 冲突解决后忘记 Commit

手动修改完冲突文件,并不代表合并已经完成。 通常还需要:
git add .
git commit
其中:
  • git add 表示告诉 Git:冲突已经解决;
  • git commit 表示正式创建合并提交。

3. 推送失败后直接 Force Push

错误示例:
git push --force origin master
除非明确知道远程提交可以被覆盖,否则不建议使用。 团队协作中,强制推送可能覆盖其他人的工作。

4. 不看状态,连续尝试各种命令

例如连续执行:
git pull
git pull --rebase
git merge
git push
这样容易让仓库进入更复杂的中间状态。 更稳妥的方式是每一步之后都执行:
git status
确认当前状态,再决定下一步。

十三、推荐的安全处理模板

以后遇到类似的 non-fast-forward,可以按照下面的流程处理。

第一步:查看状态

git status

第二步:查看远程更新

git fetch origin

第三步:查看本地与远程提交关系

git log --oneline --graph --decorate --all -20

第四步:选择 Merge 或 Rebase

使用 Merge:
git merge origin/master
或者使用 Rebase:
git rebase origin/master
也可以直接:
git pull origin master
或者:
git pull --rebase origin master

第五步:如果发生冲突,解决冲突

查看冲突文件:
git status
修改完成后:
git add .
如果是 Merge:
git commit
如果是 Rebase:
git rebase --continue

第六步:推送

git push origin master

十四、本次问题的核心结论

本次问题并不是简单的“远程代码没有拉取”,而是经历了以下过程:
  1. 本地和远程各自有一个新提交;
  2. 本地和远程分支发生分叉;
  3. 直接 Push 被 Git 拒绝;
  4. 执行 Pull 后开始合并;
  5. 合并冲突已经解决;
  6. 但还没有创建合并提交;
  7. 仓库因此停留在 MERGING 状态;
  8. git pull --rebase 无法在未完成的 Merge 中执行;
  9. git push 也因为提交历史尚未完成整合而继续失败;
  10. 执行 git commit 正式完成合并;
  11. 再执行 git push,推送成功。
最终使用的命令是:
git commit -m "Merge origin/master into master"
git push origin master
其中第一条命令负责完成当前合并,第二条命令负责将最终结果推送到远程仓库。 理解这次问题后,可以发现 Git 的错误提示其实已经非常明确:
All conflicts fixed but you are still merging.
(use "git commit" to conclude merge)
真正需要做的并不是继续 Pull,而是先把已经开始的 Merge 正式结束。

发表评论