プログラミング

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です。作業中の変更はありません。次のうち、両方の変更を残すために必要な流れを説明してください。

  1. 同じpushを何度も繰り返す。
  2. 相手の履歴を取得・確認し、手元へ統合してから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には届いたのにサイトが古い場合は、公開サイトへ反映されないときの確認で次の工程を調べます。

← 記事一覧へ戻る