共同開発で起きやすいのが「ローカルとリモートの履歴が食い違った」状態です。
直近の変更を取り込まずに作業を進めた結果、pushが拒否されたり、pull後のマージコミットが増えすぎて履歴が読みにくくなることがあります。
本記事では、履歴のズレを正しく理解し、git fetch
・git pull
・git rebase
を状況に応じて使い分けて同期する手順を整理します。
食い違いを見極めるための前提確認
まずはローカルとリモートの先祖関係を把握します。
可視化オプションを加えたログ表示と、現在の参照位置を確認することで、どちらが先行しているか、互いに分岐しているかを見極めます。
# 参照の位置関係を確認
git fetch --prune
git status
git log --oneline --decorate --graph --all -n 30
pushが拒否された場合はメッセージに「non-fast-forward」や「fetch first」などのヒントが出ます。
その場合はリモートの変更を取り込んだ上で履歴を整え、再度pushを試みます。
ケースA:リモートが先行している(自分は変更していない)
自分の作業ブランチにローカルコミットがなく、リモートだけが進んでいる場合は、単純な早送りで同期できます。
余計なマージコミットを作らないために、fetch後のfast-forwardのみを行うのが最もシンプルです。
# リモートの更新を取得して早送り(fast-forward)
git fetch --prune
git merge --ff-only origin/main # 例:mainブランチ
pullで一度に行う場合も、マージコミットを作らない設定を有効にしておくと履歴がきれいになります。
# pullが可能ならfast-forwardのみで反映
git pull --ff-only
ケースB:自分が先行していて、リモートには新規コミットがない
ローカルでコミットを積んでおり、リモートに新着がない場合は、そのままpushできます。
拒否が出る場合は保護ルールや権限設定を確認します。
git push origin HEAD
ケースC:互いに分岐している(双方に新しいコミットがある)
自分もリモートも進んでおり、共通祖先から分岐しているときは、取り込み方を「マージ」か「リベース」から選びます。
履歴を一本化して読みやすさを重視するならリベース、マージ点を残して統合の履歴を明確化したいならマージを選びます。
マージで取り込む場合(履歴は分岐と統合が残る)
既定のpullはマージです。コンフリクトが出たら解消し、マージコミットが一つ追加されます。
チームで「マージ派」を採用しているなら、この運用が分かりやすい選択です。
# マージで取り込む(デフォルト)
git pull
# コンフリクト時は修正→ステージ→コミット
git add .
git commit
リベースで取り込む場合(直線的な履歴に整える)
自分のコミット列をリモート先端の上に付け替えることで、履歴が直線化します。
履歴が読みやすくなり、レビューも容易になる一方で、公開済みブランチの書き換えには注意が必要です。
# 一回限りのリベース取り込み
git fetch --prune
git rebase origin/main
# pull時に常にリベースしたい場合の設定
git config --global pull.rebase true
# 作業中の未ステージ変更を自動退避してからリベースしたい場合
git config --global rebase.autoStash true
リベース中にコンフリクトが起きたら、該当ファイルを解消して続行します。
どうしても難しい場合は中断してやり直すこともできます。
# 解消して続行
git add .
git rebase --continue
# 中断して開始前に戻す
git rebase --abort
ケースD:pull時に「non-fast-forward」でpushできない
リベースやマージでリモートの変更を取り込んだ後、pushが拒否されることがあります。
直前に誰かがさらに更新した可能性があるため、再度fetchして差分を解消します。
それでも自分側で履歴書き換えを行った場合は、チーム合意のうえで--force-with-lease
を使います。
# まずは最新を取得して差分を解消
git fetch --prune
git rebase origin/main # もしくは git merge origin/main
# 公開ブランチの書き換えが必要な場合の最終手段
git push --force-with-lease
--force
ではなく--force-with-lease
を使うことで、他者の更新を誤って上書きするリスクを抑えられます。
ただし保護ブランチでは禁止されていることが多く、基本はPR上で解決するのが安全です。
ケースE:ローカル作業を残したままリモートに合わせたい
作業途中の変更がワーキングツリーにある場合は、stash
や自動退避を活用します。
リベース時に未コミットの変更が邪魔になるときに有効です。
# 一時退避してから同期
git stash push -m "wip"
git fetch --prune
git rebase origin/main
git stash pop
設定で自動退避を有効にしておけば、毎回の手動操作を減らせます。
git config --global rebase.autoStash true
運用ポリシーの選び方
直線的で読みやすい履歴を重視するならpullの既定をリベースにし、レビューのしやすさとバイセクションの容易さを確保します。
反対に、統合の履歴を明示的に残したいチームや、複雑なブランチ戦略を取る現場ではマージが適します。
いずれの場合も、保護ブランチの設定、CIの必須化、fetch.prune
による参照掃除を合わせて導入すると、同期トラブルは大きく減ります。
# 参考:参照掃除とpull戦略の推奨設定例
git config --global fetch.prune true
git config --global pull.rebase true # 直線履歴派の例
# git config --global pull.rebase false # マージ派はこちら
まとめ
履歴の食い違いは、まずfetch
で最新を取得し、先祖関係を把握してから「マージ」か「リベース」を選ぶのが基本です。
fast-forward可能なときは--ff-only
で安全に進め、分岐しているときはチーム方針に沿って取り込みます。
どうしても履歴書き換えが必要な場面では--force-with-lease
と保護ブランチの運用を徹底し、pull.rebase
やrebase.autoStash
などの設定で日々の同期コストを下げることで、安定したコラボレーションを実現できます。