Git pushのrejected・non-fast-forwardを直す方法|履歴を消さずに取り込む
git pushがnon-fast-forwardで拒否されたときの確認手順。未コミット変更を守り、fetchで履歴を調べて統合する方法、競合とmerge --abort、練習の解答を解説します。
git pushで、次の表示が出て止まった。何回やり直しても送れないと、困りますよね。
! [rejected] main -> main (non-fast-forward)
error: failed to push some refs
この場合は、送信先と手元の履歴がどう違うかを確認します。Gitは、通常のpushで相手側の履歴を失わないように更新を拒否しています。GitHub公式:non-fast-forwardエラー
この記事は、同じリポジトリから作業を始めたケースを扱います。originは接続先の名前、mainはブランチ名の例です。自分の環境で確認し、違う場合は読み替えてください。
rejectedの後ろにある理由を読む
rejectedは拒否されたことを示す表示で、原因は1つではありません。
| メッセージの例 | 調べること |
|---|---|
non-fast-forward、fetch first |
相手側と手元のコミットの違い |
Authentication failed |
接続の認証方法や認証状態 |
Repository not found |
接続URLとアクセス権 |
protected branch、ルール違反 |
直接pushできるか、レビューが必要か |
認証や保護ルールの問題を、履歴の取り込みで直そうとしても解決しません。今回の手順へ進む前に、末尾の具体的な理由を読みます。
コミットは変更を履歴へ記録した1件、ブランチはその履歴の流れに付けた名前です。基本操作がまだ曖昧なら、pushとpullの入門を先に確認できます。
作業中の変更と送信先を確かめる
まず次を実行します。
git status
git branch --show-current
git remote -v
git statusに未コミット変更や未追跡ファイルが出たら、その内容を確認します。必要な作業はコミットするか、プロジェクトの外へ控えを残すなどして保護し、履歴を統合する前に作業状態を整えます。削除してきれいにする必要はありません。
途中のmergeやrebaseが表示されているなら、別の取り込みを重ねず、その作業を先に確認してください。ここからの例は、作業が整ったmainで進めます。
git branch --show-currentが空なら、通常のブランチにいない状態かもしれません。想定と違うときに、そのままmainへ操作を続けないようにします。
fetchで相手の履歴を取得して比べる
git fetch origin
git log --oneline --graph --decorate --all -12
fetchは相手の履歴を取得する操作です。origin/mainという取得済み情報は更新しますが、現在の作業ブランチへ自動で統合はしません。Git公式:git fetch
例えば、共通のAからそれぞれ変更が増えると、次の状態になります。A・B・Cは説明用のコミット名です。
GitHub側: A ── B
手元側: A ── C
Bを含まないCで相手を更新しようとするため、通常のpushは止まります。1人でも、GitHubのWeb画面や別のPCで編集したときに起こります。
より絞って差を読むなら、次の表示も使えます。
git log --oneline --left-right HEAD...origin/main
<は手元側だけ、>は取得した相手側だけのコミットです。HEADは、ここでは現在の作業位置を表します。直近12件の図に共通部分が見えない場合もあるため、図だけで履歴が無関係だと決めないでください。Git公式:git log
一方向に進められる場合と、分岐した場合を分ける
手元が遅れているだけの場合
作業を整え、取得した履歴を取り込んでよいと確認できたら、次を使えます。
git merge --ff-only origin/main
--ff-onlyは、履歴の先端をそのまま進められる場合だけ取り込む指定です。両側へ別々のコミットが増えていれば停止します。停止したからといって、変更が消えたわけではありません。
両側で別々に進んでいる場合
履歴と内容を確認し、両方の変更をまとめる方針なら、マージします。
git merge origin/main
マージは、別の履歴の変更を現在のブランチへ統合する操作です。分岐した履歴をまとめるときは、両方を親に持つ新しいコミットが作られます。メッセージ用エディターが開いたら、内容を確認して保存・終了します。Git公式:git merge
共通の履歴がないというエラーが出た場合は、接続先やリポジトリの作り方を見直します。別プロジェクトの履歴を、意味を確認せず混ぜないでください。
競合が出たら、完成形を決めてから記録する
CONFLICTと出たら、同じ部分の変更などをGitだけで決められない状態です。まずgit statusで対象を確認します。
READMEなら、次のような印が入ることがあります。
<<<<<<< HEAD
Pythonの学習ノートです。
=======
JavaScriptの制作メモです。
>>>>>>> origin/main
両方が必要なら「PythonとJavaScriptの学習・制作メモです。」など、目的に合う文章へ直し、印も取り除きます。「自分の変更を全部採用」ボタンで済ませず、相手が何を追加したか読みます。
READMEを解決した場合の例です。ほかのファイルが競合していれば、そのファイルも確認して個別に追加します。
git add README.md
git diff --staged
git status
git commit -m "リモートの変更を統合"
プログラムの競合なら、記録前に実行やテストで動作も確かめます。判断できず取りやめたい場合は、進行中のマージをgit merge --abortで中止できます。ただし、開始前の未コミット変更を完全に戻せない場合もあります。先に作業を整えるのは、そのためです。
最後にpushして、GitHubで確認する
統合と確認が終わったら実行します。
git status
git push origin main
GitHubで、対象ブランチに手元と相手の変更が残っているか確認します。統合中に相手へさらにコミットが増えた場合は、再び拒否されることがあります。そのときも最新履歴を取得して、増えた分を確かめます。
push --forceは通常の安全確認を外し、相手側のコミットを失わせる可能性があります。今回の「相手の変更も残して送る」という目的には使いません。Git公式:git push
練習:次に行う操作を選ぶ
GitHub側はA→B、手元はA→Cです。作業中の変更はありません。次のうち、両方の変更を残すために必要な流れを説明してください。
- 同じpushを何度も繰り返す。
- 相手の履歴を取得・確認し、手元へ統合してからpushする。
模範解答
2です。例と同じorigin・mainなら、次の流れになります。
git fetch origin
git log --oneline --graph --decorate --all -12
git merge origin/main
競合があれば解決・確認・コミットを行い、その後でgit push origin mainします。
解説
取得だけではBとCは統合されません。マージによって両方の変更を含む履歴にする必要があります。同じpushを繰り返しても、この関係は変わりません。
ブランチと統合を小さな例で練習したい場合は、Gitのブランチとマージへ進んでみてください。GitHubには届いたのにサイトが古い場合は、公開サイトへ反映されないときの確認で次の工程を調べます。