git pushを実行すると次のようなエラーでpushが拒否されることがあります。
$ git push origin main To github.com:user/repo.git ! [rejected] main -> main (non-fast-forward) error: failed to push some refs to 'github.com:user/repo.git' hint: Updates were rejected because the tip of your current branch is behind hint: its remote counterpart. Integrate the remote changes (e.g. hint: 'git pull ...') before pushing again.
このエラーは「早送り(fast-forward)できない」という意味で、リモートが自分より先行している/互いに分岐している状態で発生します。対処は基本的に最新を取り込んでからpushし直すだけですが、「リモート先行」「互いに分岐」「履歴書き換え後」「保護ブランチ」など原因によって選ぶべき手段が変わります。
この記事では、non-fast-forwardの6つの発生パターンと判定フロー、--force-with-leaseの安全な使い方、そして同じ問題を起こさないための運用設定まで実務で迷わない形で解説します。
この記事で学べること
- fast-forward/non-fast-forwardの仕組みと判定基準
- 6つの発生パターンと個別対処
pull --rebase/pull --ff-only/mergeの使い分け--forceと--force-with-leaseの安全性の違い- amend/rebase/resetで自分が履歴を書き換えた場合の対処
- 保護ブランチ/tagのnon-ff
pull.rebase・fetch.prune等の予防設定
fast-forward/non-fast-forwardの仕組み
Gitのbranch先端を「ポインタ」と呼び、pushはリモートのポインタを進める操作です。リモートの先端がローカルの先祖に含まれている場合はポインタを進めるだけで済むのでfast-forward。そうでない場合は新しい履歴をマージ/リベースしないと整合できないので、安全装置としてpushが拒否されます。
# fast-forward OK: リモート先端が自分の先祖 # A - B - C ← origin/main # \ # D - E ← HEAD (localmain) # → origin/main を E まで進めるだけ # non-fast-forward: リモートに自分の知らないコミットあり # A - B - C - X - Y ← origin/main (他メンバーがpush) # \ # D - E ← HEAD (local main) # → 進めるだけでは同期できない(X,Yが捨てられる) # → Gitが安全のため拒否
ポイント:non-fast-forwardは「拒否された=エラー」ではなく、「データ喪失を防ぐための安全装置」です。他メンバーのコミットを意図せず消さないよう、Gitが仕組み上止めてくれています。対処するときも「リモートのコミットをどう扱うか」を意識して選びましょう。
6つの発生パターン早見表
ポイント:8〜9割は①+②(同期漏れ)です。git pull --rebaseが効く典型パターンなので、まずこれを試してみましょう。「自分が履歴を書き換えた(amend/rebase)」が原因なら③で--force-with-lease、mainへ直接pushしようとしているなら④でPRへ切り替えが正解です。
STEP 0:原因を診断する
# 最新情報を取得 git fetch --prune # 先祖関係と件数を一目で把握 git rev-list --left-right --count origin/main...HEAD # 出力例: "3 2" ← リモート3件先行、ローカル2件固有 # 分岐状況の可視化 git log --oneline --graph --decorate --all -20 # 自分のブランチ固有コミットを確認 git log --oneline origin/main..HEAD # リモート固有コミットを確認 git log --oneline HEAD..origin/main
診断結果の読み方
- 右:0, 左:N → リモート先行(ケース①)
- 右:M, 左:N(両方非0)→ 互いに分岐(ケース②)
- 自分のcommitが消えた・SHAが変わった → 履歴書き換え(ケース③)
- サーバーから
protected branchメッセージ → 保護ブランチ(ケース④) no upstream branch警告 → 追跡先ミス(ケース⑤)- タグ名でrejected → タグ重複(ケース⑥)
ケース①:リモートだけ先行している
ローカルに新規コミットが無い、または未コミット変更だけでリモートだけが進んでいる状態。pullで取り込めば即解決です。
# fastforwardでpull git pull --ff-only origin main # → FFできなければエラー(想定通り) # 未コミット変更があれば先にstash git stash push -u -m "work in progress" git pull --ff-only git stash pop # 取り込めたらpush git push origin main
ポイント:--ff-onlyを付けるとFFできない場合はpullが失敗するので、知らぬ間にマージコミットが増えるのを防げます。安全側の挙動なのでgit config --global pull.ff onlyでデフォルト化もおすすめ。
ケース②:ローカルとリモートが両方進んでいる
自分がcommit中に同僚がpushした、の典型パターン。rebaseかmergeで統合してからpushします。チーム方針によりますが、個人ブランチはrebase、共有ブランチはmergeが一般的。
# fetchして最新化 git fetch --prune # リベース git rebase origin/main # conflictが出たら解消して続行 git add . git rebase --continue # 中止したいなら git rebase --abort # push(rebaseでSHAが変わったので個人ブランチ限定) git push origin HEAD
git fetch --prune git merge origin/main # conflict解消 git add . git commit # push git push
rebase/merge選択の詳細はrebaseとmergeの違いと使い分けも参照してください。
pull –rebaseで一発
# fetch+rebaseを一発で実行 git pull --rebase origin main # デフォルトrebaseにしておくと楽 git config --global pull.rebase true # 以降 git pull だけで rebase 動作 # さらにautoStashで未コミットも自動退避 git config --global rebase.autoStash true
ケース③:自分でamend/rebase/resetして履歴を書き換えた
コミットメッセージをamendした/rebaseでsquashした/resetで巻き戻したなど、自分のローカル履歴のSHAを変えた直後にpushしようとするとnon-ffになります。リモートの元のコミットを上書きする必要があるので、強制pushが必要です。
# 最新リモート情報を取得(stale防止) git fetch origin # 安全な強制push(推奨) git push --force-with-lease origin <branch> # より厳密:リモートが期待SHAと一致するときだけpush git push --force-with-lease=main:<期待SHA> origin main # 危険:無条件上書き git push --force origin <branch> # エイリアス登録(force-with-leaseをデフォルトに) git config --global alias.fp "push --force-with-lease"
警告:git push --forceは他メンバーがpushした新しいコミットを気付かずに上書き消去します。実運用では--force-with-leaseを必ず使ってください。自動エイリアス登録でデフォルトを安全側に寄せるのが推奨。
どんな時に自分で履歴を書き換えているか
履歴書き換えの代表操作
git commit --amend(直前のコミット修正)git rebase(特にインタラクティブ-iでsquash/reword)git resetでpush済みコミットを巻き戻しgit cherry-pickで他branchの内容を取り込んだ後、元を戻そうとするgit filter-repo/filter-branchで履歴一括書き換え
メッセージ修正の詳細はコミットメッセージの変更方法、push取り消し全般はpushを取り消す方法も参照。
ケース④:保護ブランチで拒否
GitHub/GitLab等のBranch protection rulesでmain/developがprotectedだと、force pushや直接pushが許可されていません。エラーメッセージにprotected branchが含まれていればこれです。
remote: error: GH006: Protected branch update failed for refs/heads/main. remote: error: Required status check "ci/build" is expected. ! [remote rejected] main -> main (protected branch hook declined)
対処方法
- 作業ブランチを切ってPRを作成(推奨)
- CIの必須チェックをパスさせる
- レビュー数を満たす
- 管理者権限で一時解除するのは最終手段
- force push禁止設定は残したままPRで統合するのが長期的に安定
# mainに直接pushしたかった変更を救出 git switch -c feature/from-main git push -u origin feature/from-main # 元のmainはリモートに合わせる git switch main git reset --hard origin/main # feature側でPR→レビュー→マージ
ケース⑤:追跡先ミス/upstream未設定
ブランチ名をリネームした、別リポで作業した、などupstream(追跡先)の設定が合っていないとnon-ffに見えることがあります。
# 現在の追跡先を確認
git branch -vv
# または
git rev-parse --abbrev-ref --symbolic-full-name @{u}
# 追跡先を明示指定
git branch --set-upstream-to=origin/main
# push先を都度指定したい場合
git push origin HEAD:main
# 新ブランチ初回push時にupstream設定
git push -u origin feature/xxx
リネーム後のpushエラーはブランチ名を変更したらpushできなくなったときの対処法で詳細を解説しています。
ケース⑥:タグの重複push
同名タグがすでにリモートに存在する場合、push時にrejectedが出ます。
! [rejected] v1.0.0 -> v1.0.0 (already exists) hint: Updates were rejected because the tag already exists in the remote.
# 新しいバージョンを採る(推奨) git tag v1.0.1 git push origin v1.0.1 # どうしても上書きしたい場合(慎重) git push --force origin v1.0.0
注意:タグはリリースポイントの固定参照。一度pushしたタグを上書きすると、既に動いているCI/デプロイ/依存リポとの整合性が崩れます。やむを得ない場合以外は、新しいバージョン番号を採るのが安全です。
予防:設定と運用で再発を減らす
運用設定のベストプラクティス
pull.rebase = true:pullでrebaseを優先、余計なマージコミット防止pull.ff = only:pull時にFFできない場合はエラーで止めるrebase.autoStash = true:rebase時に未コミットを自動退避fetch.prune = true:削除済みリモート追跡の自動整理push.autoSetupRemote = true(Git 2.37+):初回pushで自動upstreamalias.fp=push --force-with-lease:強制pushを安全側へ- PRベースのマージ運用:保護ブランチで直接pushを禁止
# pull系 git config --global pull.rebase true git config --global pull.ff only git config --global rebase.autoStash true # fetch系 git config --global fetch.prune true # push系(Git 2.37+) git config --global push.autoSetupRemote true # 便利エイリアス git config --global alias.fp "push --force-with-lease"
実践シナリオ
シナリオ① 朝一push、同僚の変更と競合
git fetch origin git pull --rebase origin main # conflict が出たら解消→continue git push origin main
シナリオ② amend後にpushがrejected
# 直前のcommitメッセージをamendした直後 git commit --amend -m "fix: 正しいメッセージ" git push origin <branch> # → rejected (non-fast-forward) # 個人ブランチなので安全な強制push git fetch origin git push --force-with-lease origin <branch>
シナリオ③ mainへ直接pushしてprotected rejected
# mainで作業してしまった変更を退避 git switch -c feature/accidental-main-work git push -u origin feature/accidental-main-work # mainをリモートに合わせる git switch main git reset --hard origin/main # PRを作ってレビュー→マージ
シナリオ④ rebase後、さらに他メンバーがpush
git rebase origin/main git push --force-with-lease # → rejected(リモートが自分のorigin/mainと違う) # 他メンバーがさらにpushしていた → fetchし直して再rebase git fetch origin git rebase origin/main git push --force-with-lease
やってはいけない落とし穴
-f/–forceを無条件で打つ
git push --forceは他メンバーの新しいcommitを気付かずに上書きします。常に--force-with-leaseを使う習慣を付け、エイリアスでgit fp→push --force-with-leaseにしておけば事故を防げます。共有ブランチでは保護ブランチルールで物理的に禁止するのが理想です。
force push後にチーム通知を怠る
個人ブランチでforce-with-leaseしていても、他メンバーがそのブランチをベースに作業していた場合、pullでエラーが起きます。履歴を書き換えたらSlackや口頭で「xxxブランチを強制pushしました」と周知し、必要なら同期コマンド(git fetch && git reset --hard origin/xxx)を案内しましょう。
rebase中にさらに別作業を始める
rebaseでconflictが出ている最中に別ブランチへ切替→別作業、と進めるとrebaseの状態が放置されます。git statusのhintを読んで--continue/--abortで完了させてから次の操作に進みましょう。
mainへ毎回force pushしてprotected解除を繰り返す
「面倒だから」と保護ブランチルールを頻繁に解除すると、CI未通過コードや未レビュー変更がmainに入ってしまい、本番障害の温床になります。PR経由でのマージを基本とし、緊急時だけ管理者権限で一時的に解除するルールが健全です。
pullでマージコミットが増え続ける
git pullのデフォルト挙動はmergeなので、pullする度にマージコミットが積まれる場合があります。履歴を直線的に保ちたいならpull.rebase = trueを設定しましょう。チーム方針として統一するのがおすすめです。
よくある質問
--rebase、ローカルに変更がない(FFだけで取り込みたい)場合は--ff-onlyが安全。常に--ff-onlyをデフォルトにし、必要な時だけ--rebaseを明示するのも良い運用。git fetch originで情報を最新化してから再実行してください。それでも失敗するなら、他メンバーが間にpushしている可能性あり。git fetch→git merge origin/mainでマージコミットが作られて統合されます。conflictが出たら解消→commit→push。履歴を直線的にしたい場合はrebaseを選びます。git rebase --abortで中止して、mergeで取り込むのも選択肢です。rebaseは直線的な履歴を得るための手段なので、commit数が多くconflictだらけなら無理にrebaseせずmergeに切り替えた方が早いことも。pull.rebase=merges(またはinteractive)にするとマージコミットを保持したままrebaseできます。チームが頻繁にmergeする運用なら有効な設定です。関連記事
- 【Git】「failed to push some refs to」エラーの原因と対処法 — 外側サマリーからの8パターン診断
- 【Git】pushを取り消す方法 — push後の対処全般
- 【Git】リモートとローカルの履歴が食い違ったときの同期方法 — pull戦略の選び方
- 【Git】コミットメッセージの変更方法 — amendで書き換え→force-with-lease
- 【Git】rebaseとmergeの違いと使い分け — 統合戦略の選択
- 【Git】「refusing to merge unrelated histories」エラーの対処法 — 関連するpullエラー
- 【Git】mergeコミットを取り消して履歴を元に戻す方法 — マージ取消後のpush
- 【Git】よく使うgitコマンドまとめ — 日常コマンドの早見表
まとめ
- non-fast-forwardはデータ喪失を防ぐ安全装置、エラーではない
- 6パターン:①リモート先行/②互いに分岐/③履歴書き換え/④保護ブランチ/⑤upstream/⑥タグ
- 最多は①+②——
git pull --rebase→push で解決 - 履歴書き換え後は
push --force-with-lease、--forceは避ける - 保護ブランチはPR経由で取り込む(force解除は最終手段)
- 予防:
pull.rebase・pull.ff=only・rebase.autoStash・fetch.prune・push.autoSetupRemoteを設定 - force pushしたら必ずチーム通知+同期手順を案内
non-fast-forwardは一見怖いですが、パターンを見分けて適切な対処を選べば大半は数分で解決します。最頻出パターンは「pullを忘れていた」だけなのでgit pull --rebaseで終わることが多いはず。履歴書き換え後は--force-with-leaseを習慣化し、保護ブランチへの直接pushはPR経由に切り替えれば、チーム開発で事故を大幅に減らせます。

