Post Views: 8
在使用 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
正确思路应该是:
- 获取远程修改;
- 将本地和远程修改合并;
- 完成合并提交;
- 再推送到远程。
三、执行 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 就是合并提交。
它同时包含两个父提交:
创建
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
执行:
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
然后根据状态提示继续操作,而不是连续尝试多个
pull、
push 或
rebase 命令。
十二、常见错误操作
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
十四、本次问题的核心结论
本次问题并不是简单的“远程代码没有拉取”,而是经历了以下过程:
- 本地和远程各自有一个新提交;
- 本地和远程分支发生分叉;
- 直接 Push 被 Git 拒绝;
- 执行 Pull 后开始合并;
- 合并冲突已经解决;
- 但还没有创建合并提交;
- 仓库因此停留在
MERGING 状态;
git pull --rebase 无法在未完成的 Merge 中执行;
git push 也因为提交历史尚未完成整合而继续失败;
- 执行
git commit 正式完成合并;
- 再执行
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 正式结束。