“! [rejected] main -> main (fetch first)” 和 “(non-fast-forward)” 是 git push 最常见的报错。网上的答案是“先 pull 再 push”,这篇在本机用两个克隆加一个裸仓库把它复现出来,看清楚 pull、rebase、--force、--force-with-lease 各自改了什么、会不会丢别人的提交。
$ git push origin main (退出码 1)
To <tmp>/remote.git
! [rejected] main -> main (fetch first)
error: failed to push some refs to '<tmp>/remote.git'
hint: Updates were rejected because the remote contains work that you do not
hint: have locally. This is usually caused by another repository pushing to
hint: the same ref. If you want to integrate the remote changes, use
hint: 'git pull' before pushing again.
hint: See the 'Note about fast-forwards' in 'git push --help' for details.
$ git fetch -q && git push origin main (退出码 1)
To <tmp>/remote.git
! [rejected] main -> main (non-fast-forward)
error: failed to push some refs to '<tmp>/remote.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.
hint: See the 'Note about fast-forwards' in 'git push --help' for details.
$ git status -sb | head -1
## main...origin/main [ahead 1, behind 1]fetch first 是远程有你本地没有的提交;non-fast-forward 是你 fetch 过了,但本地和远程已经各自往前走。两种都是“分叉”。
$ git rev-parse --short HEAD
8a852e3
$ git pull --no-rebase origin main 2>&1 | head -12
From <tmp>/remote
* branch main -> FETCH_HEAD
Merge made by the 'ort' strategy.
f.txt | 1 +
1 file changed, 1 insertion(+)
$ git log --graph --oneline
* 02e2e9e Merge branch 'main' of <tmp>/remote
|\
| * 5aef7d1 bob: edit f.txt
* | 8a852e3 alice: add a.txt
|/
* 422385c base
$ git push -q origin main && echo "push 成功"
push 成功
$ git pull --rebase origin main 2>&1 | head -6
From <tmp>/remote
* branch main -> FETCH_HEAD
422385c..5aef7d1 main -> origin/main
Rebasing (1/1)
Successfully rebased and updated refs/heads/main.
$ git log --graph --oneline
* bbc4ebe alice: add a.txt
* 5aef7d1 bob: edit f.txt
* 422385c base
Alice 的提交 "alice: add a.txt" 的哈希:rebase 前 8a852e3,rebase 后 bbc4ebe(内容一样,父提交变了,哈希就变)
$ git push -q origin main && echo "push 成功"
push 成功合并多一个合并提交、原提交哈希不变;变基历史是一条线,但本地提交会换哈希——已经推给别人的提交不要变基。
$ git push --force origin main
To <tmp>/remote.git
+ 5aef7d1...8a852e3 main -> main (forced update)
$ git fetch -q && git log --oneline origin/main
8a852e3 alice: add a.txt
422385c base
$ git -C remote.git log --oneline main
8a852e3 alice: add a.txt
422385c base
—— 换成 --force-with-lease(Alice 本地只有旧的 origin/main,Bob 已经在远程推了新提交)
$ git push --force-with-lease origin main (退出码 1)
To <tmp>/remote.git
! [rejected] main -> main (stale info)
error: failed to push some refs to '<tmp>/remote.git'
—— Alice 只做了一次 git fetch,没看内容,再 --force-with-lease
$ git fetch -q && git push --force-with-lease origin main
To <tmp>/remote.git
+ 5aef7d1...8a852e3 main -> main (forced update)
$ git -C remote.git log --oneline main
8a852e3 alice: add a.txt
422385c base
—— 指定“我以为远程是这个提交”才算数:--force-with-lease=main:<提交>
$ git fetch -q && git push --force-with-lease=main:422385c origin main (退出码 1)
To <tmp>/remote.git
! [rejected] main -> main (stale info)
error: failed to push some refs to '<tmp>/remote.git'--force 无条件覆盖;--force-with-lease 只在你本地记录的远程值没变时放行,但 fetch 会刷新这个记录,所以要写成 --force-with-lease=分支:提交 才可靠。
$ git pull 2>&1 | tail -6
hint:
hint: You can replace "git config" with "git config --global" to set a default
hint: preference for all repositories. You can also pass --rebase, --no-rebase,
hint: or --ff-only on the command line to override the configured default per
hint: invocation.
fatal: Need to specify how to reconcile divergent branches.
$ git config pull.rebase false && git pull 2>&1 | head -4
Merge made by the 'ort' strategy.
f.txt | 1 +
1 file changed, 1 insertion(+)
$ git config pull.ff only && git pull 2>&1 | tail -4
hint: git rebase
hint:
hint: Disable this message with "git config set advice.diverging false"
fatal: Not possible to fast-forward, aborting.
$ git config pull.rebase true && git pull 2>&1 | head -4 && git log --oneline
From <tmp>/remote
422385c..5aef7d1 main -> origin/main
Rebasing (1/1)
Successfully rebased and updated refs/heads/main.
bbc4ebe alice: add a.txt
5aef7d1 bob: edit f.txt
422385c base分叉时直接 fatal,要自己设 pull.rebase 或 pull.ff。
$ git -C remote.git config receive.denyNonFastForwards true
$ git push --force origin main (退出码 1)
remote: error: denying non-fast-forward refs/heads/main (you should pull first)
To <tmp>/remote.git
! [remote rejected] main -> main (non-fast-forward)
error: failed to push some refs to '<tmp>/remote.git'
$ git -C remote.git config receive.denyNonFastForwards false; printf '#!/bin/sh\necho "pre-receive: 这个分支需要走合并请求" >&2\nexit 1\n' > remote.git/hooks/pre-receive && chmod +x remote.git/hooks/pre-receive
$ git fetch -q && git pull -q --no-rebase origin main && git push origin main (退出码 1)
remote: pre-receive: 这个分支需要走合并请求
To <tmp>/remote.git
! [remote rejected] main -> main (pre-receive hook declined)
error: failed to push some refs to '<tmp>/remote.git'[remote rejected] 是服务端的规则(denyNonFastForwards、钩子)拒的,本地没法用 --force 绕过。