Gitリモート入門:push・pull・fetchと拒否されたpushの直し方
ローカルの bare リポジトリでオフライン再現。fetch と pull の違い、non-fast-forward で拒否された push、upstream、pull.rebase の選び方、clone サイズの実測まで。
シリーズ:Git 講座 3/6(中級)。 コミットとブランチには慣れた(講座1・2)けれど、push と pull はおまじないのように打っている人向けです。origin/main の正体、push が拒否される理由、fetch と pull の違い、そして pull の設定の選び方が分かるようになります。
git push が拒否されて手が止まった人、git pull したら誰も望んでいないマージコミットができた人、「force push は絶対ダメ」と言われたけれど理由は聞いていない人のための記事です。例はすべてオフラインで動きます。「サーバー」はローカルフォルダに置いた bare リポジトリで、2つの clone alice と bob を同じチームの2人に見立てます。表示されるメッセージは、認証の部分を除けば GitHub などのホスティングサービスで見るものと同じです。
実行環境は Git 2.54.0(Apple Git-157)、Apple Silicon Mac(M1 Pro)、デモ用 ID、使い捨てフォルダのみ。出力中のローカルパスは ~/demo/… に短縮しています。
壊しても平気なリモートを作る
リモートとは、自分のリポジトリが名前で知っている別のリポジトリです。普通は origin という名前です。bare リポジトリは作業ツリーを持たず、.git の中身だけでできています。サーバー上では誰もファイルを編集しないので bare を使います。
git init --bare -b main remote.git
git clone remote.git alice
Initialized empty Git repository in ~/demo/rm3/remote.git/
Cloning into 'alice'...
warning: You appear to have cloned an empty repository.
done.
この警告は無害です。Alice がコミットして push します。
cd alice
printf 'orders = []\n' > shop.py; git add shop.py; git commit -qm "Start shop"
git push
git branch -vv
To ~/demo/rm3/remote.git
* [new branch] main -> main
* main edd9011 [origin/main] Start shop
[origin/main] は main の upstream(追跡ブランチ)です。push・pull・status が既定で比べる相手のリモートブランチで、git clone が設定してくれました。
なぜ普通の(bare でない)リポジトリに push しないのか。試すとサーバー側が拒否しました。
remote: error: refusing to update checked out branch: refs/heads/main
remote: error: By default, updating the current branch in a non-bare repository
remote: is denied, because it will make the index and work tree inconsistent
...
! [remote rejected] main -> main (branch is currently checked out)
upstream と新しいブランチの最初の push
新しいブランチには、設定するまで upstream がありません。
git switch -c feature/cart
# ... コミット ...
git push
fatal: The current branch feature/cart has no upstream branch.
To push the current branch and set the remote as upstream, use
git push --set-upstream origin feature/cart
To have this happen automatically for branches without a tracking
upstream, see 'push.autoSetupRemote' in 'git help config'.
表示どおり git push -u origin feature/cart とするか、push.autoSetupRemote=true を一度設定します(Git 2.37以降)。設定後は同じ git push が次のように表示しました。
* [new branch] feature/cart -> feature/cart
branch 'feature/cart' set up to track 'origin/feature/cart'.
もう1つの定番は、存在しないブランチ名を push することです。古い解説の多くは master と書いていますが、新しいリポジトリは main です。
$ git push origin master
error: src refspec master does not match any
error: failed to push some refs to '~/demo/rm3/remote.git'
「src refspec … does not match any」は、その名前のローカルブランチがないという意味です。git branch で確認しましょう。
fetch と pull:origin/main の正体
origin/main はリモート追跡ブランチです。最後に origin と通信したとき main がどこにあったかを手元に控えた、読み取り専用のメモです。動くのは fetch(または最初に fetch を行う pull)のときだけです。
Alice がコミットを push しました。Bob はまだ fetch していません。
cd ../bob
git status
On branch main
Your branch is up to date with 'origin/main'.
ここが落とし穴です。「up to date」はサーバーではなく、Bob の手元の古い origin/main と比べた結果です。fetch すると変わります。
git fetch
git status -sb
git log --oneline main..origin/main
From ~/demo/rm3/remote
edd9011..8500a27 main -> origin/main
## main...origin/main [behind 1]
8500a27 Add TAX
fetch はコミットをダウンロードして origin/main を動かしただけで、Bob の main もファイルも変わっていません。中身を確認してから(git log main..origin/main、git diff main origin/main)git merge で取り込めます。今回は fast-forward でした。git pull は git fetch とこの取り込みを続けて実行するだけのコマンドです。
| やりたいこと | コマンド | ブランチ・ファイルに触れるか |
|---|---|---|
| 同僚が push した内容を安全に見る | git fetch |
触れない |
| 取り込む前に比べる | git log main..origin/main、git diff main origin/main |
触れない |
| 同僚の作業を自分のブランチへ | git pull(fetch + merge/rebase) |
触れる |
| fast-forward のときだけ受け入れる | git pull --ff-only |
触れる、または失敗 |
| サーバーの今の状態を確認 | git ls-remote origin |
触れない |
拒否された push と取り込み方
今度は2人が同時に作業します。Alice は送料を追加して先に push しました。Bob は config.py を追加してコミットし、push します。
$ git push
To ~/demo/rm3/remote.git
! [rejected] main -> main (fetch first)
error: failed to push some refs to '~/demo/rm3/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.
Bob の push を受け入れると、サーバーの main から Alice のコミットが抜け落ちるので Git は拒否します。pull の設定がない状態で Bob が git pull を実行すると:
From ~/demo/rm3/remote
8500a27..b2e3cbf main -> origin/main
hint: You have divergent branches and need to specify how to reconcile them.
hint: You can do so by running one of the following commands sometime before
hint: your next pull:
hint:
hint: git config pull.rebase false # merge
hint: git config pull.rebase true # rebase
hint: git config pull.ff only # fast-forward only
...
fatal: Need to specify how to reconcile divergent branches.
最初の2行に注目してください。fetch はもう終わっています。 この状態でもう一度 push すると、理由が (fetch first) から (non-fast-forward) に変わります。Git がリモートのコミットを知ったうえで、Bob のブランチが遅れていると判断したためです。git status -sb は [ahead 1, behind 1] でした。
Bob のリポジトリを2つコピーして、両方の答えを試しました。
マージ(git pull --no-rebase):
* c70294d Merge branch 'main' of ~/demo/rm3/remote
|\
| * b2e3cbf Add shipping fee
* | a556d35 Add currency
|/
* 8500a27 Add TAX
リベース(git pull --rebase):
Successfully rebased and updated refs/heads/main.
* 50a55ed Add currency
* b2e3cbf Add shipping fee
* 8500a27 Add TAX
* edd9011 Start shop
リベースでは Bob のコミットが Alice のコミットの上に積み直され、ID が変わりました(a556d35 → 50a55ed)。履歴は一直線になり、push も通りました(b2e3cbf..50a55ed main -> main)。マージ版でも動きますが、pull のたびに中身のない「Merge branch 'main' of …」コミットが増えていきます。
| 設定 | 分岐時の git pull の動作 |
向いている場面 |
|---|---|---|
pull.rebase true |
手元のコミットを上に積み直す | 多くのチーム。未公開の作業をきれいに保つ |
pull.rebase false |
マージコミットを作る | 手元のコミットを別の場所ですでに共有している |
pull.ff only |
fast-forward 以外は拒否 | 自分ではコミットしないブランチ(デプロイ用の checkout など) |
| 未設定(2.54.0) | fatal: Need to specify how to reconcile divergent branches. |
選択を強制する |
リベースが書き換えるのはまだ push していない自分のコミットだけなので安全です。リベースの詳細と、他人がすでに持っているコミットをリベースしてはいけない理由は講座4で扱います。
よくある失敗:「とりあえず force push」
実際に起きること。 Alice の clone は、origin/main がまだ b2e3cbf だと思っています(Bob の push を fetch していない)。Alice は直前のコミットを amend して push します。普通の push は拒否されるので、force に手を伸ばします。
--force-with-lease は、Alice が最後に見た状態がサーバーにまだあるかを先に確かめます。
$ git push --force-with-lease
! [rejected] main -> main (stale info)
error: failed to push some refs to '~/demo/rm3/remote.git'
素の --force には確認がありません。
$ git push --force
+ 50a55ed...56ed907 main -> main (forced update)
Bob のコミット 50a55ed はサーバーの main から消えました。次に Bob が pull すると + 50a55ed...56ed907 main -> origin/main (forced update) と分岐エラーが表示され、Bob の作業は彼の手元の clone にしか残っていません(検証後、サーバーはコピーから復元しました)。
対処: 共有ブランチには force push しない。自分だけの機能ブランチをリベース後に書き換えるときは git push --force-with-lease を使います。直前に git fetch したなら、届いた内容を先に確認します(git log HEAD..origin/<ブランチ>)。fetch すると lease が比べる値が更新され、保護が効かなくなるからです。
実測:通常・shallow・blobless の clone
リモートには「どれだけダウンロードするか」という面もあります。CSV ファイル1つに毎回20行ずつ追記する2,000コミットのリポジトリを生成しました(乱数シード42固定、最終的に40,000行・984,486バイト、git gc 後のサーバー側パックは10.55MiB)。ハードリンクではなく実際のパック転送になるよう file:// 経由で clone し、各3回、Apple Silicon Mac(M1 Pro)、Git 2.54.0 で測りました。
| clone の種類 | 各回(ms) | .git のサイズ |
履歴のコミット数 |
|---|---|---|---|
| 通常 | 1169.4 / 1124.1 / 1221.5 | 10,936 KB | 2,000 |
--depth 1(shallow) |
128.1 / 129.6 / 140.9 | 608 KB | 1 |
--filter=blob:none(blobless) |
180.9 / 190.7 / 184.8 | 1,108 KB | 2,000 |
小さな clone にも後払いのコストがあります。
- blobless の clone で
git log -p -50を実行すると、1回目は 6,792.8ms かかりました。古い版のファイル50個をその場で取りに行くためで、.gitは25,644KBまで増えました。2回目は300.3msでした。 - shallow の clone では
git log --onelineにコミットが1つしか出ず、git rev-parse --is-shallow-repositoryはtrueでした。git fetch --unshallowは1,241.9msかかり、.gitは12,104KB、ほぼ通常 clone と同じコストになりました。 - 新着がないときの
git fetchは5回の中央値で41.5msでした。
目安:1つのコミットをビルドするだけの CI は --depth 1、履歴は必要だが古いファイルまでは要らない開発者は blobless、毎日使う作業用 clone は通常の clone。
正直な限界とバージョンの注意
- すべて Git 2.54.0 とローカルの bare リポジトリで実行しました。ホスティングサービスでは認証、ブランチ保護、独自の拒否メッセージ(保護ブランチが force push を拒否する等)が加わりますが、ここでは試していません。
push.autoSetupRemoteは Git 2.37 で追加されました。分岐時の pull がエラーになったのは 2.33/2.34 の頃で(それ以前は警告してマージしていた)、文言は 2.54.0 でのみ確認しています。--force-with-leaseは Git 1.8.5 からあります。期待値を明示しない場合は手元のorigin/mainと比べるので、直前のgit fetchで保護が弱まります。- 部分 clone(
--filter)にはサーバー側の対応が必要で、ローカルの bare リポジトリではuploadpack.allowFilter trueを設定しました。file://での時間にはネットワーク遅延が含まれないので、実際の差は普通もっと大きくなります。
練習問題
git statusは「Your branch is up to date with 'origin/main'」と言いますが、同僚は10分前に push したと言っています。自分のブランチを変えずに同僚のコミットを見るには?- push が
(non-fast-forward)で拒否されました。手元には一度も push していないコミットが2つあります。履歴を一直線に保ったままリモートの作業を取り込むコマンドは?
解答
git fetchの後にgit log --oneline main..origin/main。status は最後に fetch したorigin/mainとしか比べません。git pull --rebase(または一度だけgit config pull.rebase true)の後にgit push。2つのコミットの ID は変わりますが、他の誰も持っていないので問題ありません。
前の講座: Git 講座2:ブランチ・マージ・コンフリクト ・次の講座: Git 講座4:rebase・cherry-pick と履歴 ・シリーズの最初:Git 講座1 ・新人向け学習ロードマップに戻る。
よくある質問
git fetch と git pull の違いは何ですか?
git fetch は新しいコミットをダウンロードして origin/main などのリモート追跡ブランチを更新するだけで、自分のブランチやファイルには触れません。git pull は fetch の後に origin/main を自分のブランチへマージまたはリベースします。検証では、リモートに新しいコミットがあっても fetch 前の git status は up to date と表示し、fetch 後に behind 1 になりました。
push が fetch first や non-fast-forward で拒否されたのはなぜですか?
リモートのブランチに手元にないコミットがあり、そのまま受け入れるとそれが消えてしまうからです。git pull --rebase(マージにしたいなら --no-rebase)で先に取り込んでから、もう一度 push します。共有ブランチで --force は使いません。--force-with-lease はリモートが動いていたため (stale info) で上書きを拒否しました。
fatal: Need to specify how to reconcile divergent branches とは?
自分のブランチとリモートの両方に新しいコミットがあり、マージかリベースかを決める pull.rebase や pull.ff の設定がない状態です。コマンドごとに git pull --rebase/--no-rebase/--ff-only を指定するか、git config pull.rebase true のように一度設定します。このエラーが出た時点で fetch はすでに終わっています。