bigywater.tech
常见报错 · 21

Git push 被拒 (fetch first / non-fast-forward):pull、rebase 各改了什么,--force-with-lease 在 fetch 之后仍会覆盖别人的提交

“! [rejected] main -> main (fetch first)” 和 “(non-fast-forward)” 是 git push 最常见的报错。网上的答案是“先 pull 再 push”,这篇在本机用两个克隆加一个裸仓库把它复现出来,看清楚 pull、rebase、--force、--force-with-lease 各自改了什么、会不会丢别人的提交。

2 种
括号里的两种原因:fetch first(没看到远程新提交)与 non-fast-forward(看到了但没整合)
8a852e3 → bbc4ebe
pull --rebase 之后,同一个提交的哈希变化(内容不变,父提交变了)
照样覆盖
git fetch 之后再 --force-with-lease,Bob 的提交仍然被覆盖;写成 =main:<提交> 才会被拦

两个括号的区别

实测 · 本机执行输出
    $ 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

实测 · 本机执行输出
    $ 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 绕过。

分步演示

推送被拒
本步的对象

    本文用到的工具与边界

    • 本机 macOS 27 arm64;报错文本都是真实复现时原样捕获的(命令行工具统一用 LC_ALL=C 取英文原文,路径换成 );所有服务都是本机上自己起的、只监听 127.0.0.1,不访问外网;计时用 time.perf_counter,数字每次运行会有小幅抖动。本篇用 git 2.54.0(Apple Git-157);“远程”是本机的裸仓库,用 GIT_CONFIG_GLOBAL 与 GIT_CONFIG_NOSYSTEM 隔离了全部配置,提交时间固定,所以哈希可复现
    • 没有验证:其他 git 版本(尤其是 pull 行为在老版本里不同);GitHub、GitLab 等托管平台上受保护分支的拒绝文案(本篇只测了 denyNonFastForwards 和 pre-receive 钩子);--force-if-includes;pull.rebase=merges / interactive;submodule 与浅克隆下的行为;Windows
    京ICP备2026067502号-1京公网安备11010502063487号