fetch と pull — 2台で作業するとき
Windows のノート PC と Mac のように2台で同じリポジトリを使っていると、「Mac で直したはずなのに、こちらでは直っていない」という場面に出会います。原因はたいてい不具合ではなく、手元の付箋がまだ GitHub に追いついていないことです。この回では、GitHub の様子を見に行くだけの git fetch、その結果を読む git status と git branch -vv、手元の main を追いつかせる git pull --ff-only を1コマンドずつ見ます。最後に、2台で別々にコミットして枝分かれしたとき、push と pull --ff-only がなぜ止まるのかを確かめます。
- 上のターミナルは、Windows のノート PC で実際に打ったときと同じ出力です(Git は英語で答えます)
- 下の中で起きていることには、グラフが2つ並びます。上が GitHub 側、下が手元で、同じコミットは上下で同じ横位置に来ます
- 丸がコミット、黒い札がブランチ、ピンクの札が HEAD です。手元の点線の札
origin/mainは、手元に貼ってある GitHub の写しです - もう1台の Mac での操作は、ターミナルには何も出ず、上の GitHub 側の図だけが進みます
- 右の説明をスクロールして読み進めると、左のターミナルでコマンドが1つずつ打たれます。
→キーでも1つずつ進みます(←で1つ戻ります。スマホでは「次へ」ボタン)。最後まで進んでから→を押すと次の区間へ移ります。黄色く光っているのが、そのコマンドで変わった場所です
1. 2台で使うと、何がずれるのか
2-1 origin と origin/main の正体で見たとおり、origin は GitHub の住所に付けた呼び名で、origin/main は「最後に確かめたときの GitHub の main の位置」を手元に貼った付箋(追跡ブランチ)です。2台で作業するときに大事なのは、origin/main は PC ごとに1枚ずつあり、その PC が GitHub に問い合わせたときにしか動かないことです。
| 付箋 | どこにあるか | 動かすコマンド |
|---|---|---|
GitHub の main | GitHub に1枚 | どちらかの PC からの git push |
origin/main | PC ごとに1枚 | その PC で打った git fetch / git pull / git push |
main | PC ごとに1枚 | その PC で打った git commit / git pull |
Mac で push すると、GitHub の main と Mac の origin/main は進みますが、Windows の origin/main と main は動きません。このずれを埋めるのが、origin/main だけを動かす git fetch と、手元の main まで進める git pull です。
2. git fetch — origin/main の付箋だけを動かす
git fetch がすることは2つです。
- GitHub に問い合わせて、手元に無いコミットを取ってくる
- 手元の origin/main を、GitHub の main の位置へ動かす
手元の main・HEAD・作業ツリー・ステージには触りません。Mac で push した直後の Windows のノート PC で、fetch の前と後を見比べます。
git fetch — GitHub の様子を見に行く
もう1台の Mac で「パンを追加」をコミットして push しました。上の GitHub 側の
mainが1つ進みます。画面のターミナルは Windows のノート PC で、こちらにはまだ何も届いていません。git statusはup to date(最新です)と答えます。これは GitHub に問い合わせた結果ではなく、手元に貼ってある origin/main の付箋と比べた結果です。origin/main は前に確かめたときのまま、卵を追加に貼ってあります。$ git status On branch main Your branch is up to date with 'origin/main'. nothing to commit, working tree cleangit fetchは GitHub の様子を見に行き、手元に無いコミットを取ってきて、origin/main の付箋だけを進めます。c43e6a8..9ae3d51 main -> origin/mainは「GitHub のmainを写して、手元のorigin/mainをc43e6a8から9ae3d51へ進めた」という意味です。HEAD とmainは動いていません。$ git fetch From https://github.com/you/memo-app c43e6a8..9ae3d51 main -> origin/main同じ
git statusが、今度はbehind 'origin/main' by 1 commit(origin/main より1つ遅れている)と答えます。can be fast-forwardedは「付箋を前に進めるだけで追いつける」という意味です。$ git status On branch main Your branch is behind 'origin/main' by 1 commit, and can be fast-forwarded. (use "git pull" to update your local branch) nothing to commit, working tree clean--allを付けると、手元にあるすべての付箋が見えます。origin/mainはパンを追加に、HEAD -> mainは卵を追加に貼ってあります。origin/HEADは GitHub 側の既定のブランチを指す札で、ここでは気にしなくて構いません。$ git log --oneline --all 9ae3d51 (origin/main, origin/HEAD) パンを追加 c43e6a8 (HEAD -> main) 卵を追加 3f2a9c1 牛乳を追加作業ツリーの
todo.txtは2行のままです。fetch は手元のmainにも作業ツリーにも触らないので、書きかけがあっても、いつ打っても安全です。$ cat todo.txt 牛乳を買う 卵を買うもう一度 fetch すると、何も出ません。GitHub 側に新しいコミットが無いとき、fetch は黙って終わります。
$ git fetch
fetch の前の git status が up to date と答えたのは、status は GitHub ではなく、手元の origin/main と比べているからです。「最新です」は「最後に fetch したときの GitHub と比べて最新」という意味にすぎません。
fetch の出力の付箋の行は「GitHub 側の名前 -> 手元の名前」の形で、左にどう動いたかが出ます。
| 出力の行 | 意味 |
|---|---|
c43e6a8..9ae3d51 main -> origin/main | 手元の origin/main を c43e6a8 から 9ae3d51 へ前に進めた |
* [new branch] memo -> origin/memo | GitHub に新しいブランチがあったので、手元に origin/memo を貼った |
+ 9ae3d51...1b2c3d4 main -> origin/main (forced update) | GitHub の main が force push で付け替えられていた(第3部) |
3. git status と git branch -vv で、origin との差を読む
fetch したあとの git status は、手元の main とその上流(upstream。「どの追跡ブランチと比べるか」の設定で、clone した main なら origin/main)を比べて、4通りのどれかを答えます。git branch -vv は同じことを角かっこで見せます。
| git status の答え | git branch -vv | 次にすること |
|---|---|---|
Your branch is up to date with 'origin/main'. | [origin/main] | 何もしなくてよい |
Your branch is ahead of 'origin/main' by 1 commit. | [origin/main: ahead 1] | git push で送れる |
Your branch is behind 'origin/main' by 1 commit, and can be fast-forwarded. | [origin/main: behind 1] | git pull --ff-only で追いつける |
Your branch and 'origin/main' have diverged, … | [origin/main: ahead 1, behind 1] | 2本をつなぐ(merge か rebase。第3部) |
数字は、それぞれの側にだけあるコミットの数です。どの答えも直前の fetch の時点の GitHub と比べたものなので、fetch しないまま status を見ると、Mac の push に気づけません。
4. git pull --ff-only — 手元の main を追いつかせる
git pull は、fetch と、手元の main を origin/main に合わせる操作を続けて行うコマンドです。--ff-only を付けると、合わせ方を fast-forward(早送り。新しいコミットを作らず、付箋を前へ進めるだけで追いつくこと)に限ります。早送りで済むときだけ進め、済まないときは何も変えずに止まるという約束です。
git pull --ff-only — 手元の main を追いつかせる
Mac が push した直後の、fetch する前の状態から始めます。GitHub 側には
パンを追加があり、手元のtodo.txtはまだ2行です。この動画では、下に作業ツリー・ステージ・HEAD のコミットの中身も出しています。$ cat todo.txt 牛乳を買う 卵を買う出力は2つに分かれます。前半の
From …の2行は fetch と同じで、origin/main を進めます。後半のUpdating c43e6a8..9ae3d51とFast-forwardは、手元のmainを origin/main の位置まで前に進めたという意味で、作業ツリーの中身も入れ替わります。$ git pull --ff-only From https://github.com/you/memo-app c43e6a8..9ae3d51 main -> origin/main Updating c43e6a8..9ae3d51 Fast-forward todo.txt | 1 + 1 file changed, 1 insertion(+)todo.txtに「パンを買う」が入りました。Mac で書いた1行が、Windows のノート PC に届いたことになります。$ cat todo.txt 牛乳を買う 卵を買う パンを買うHEAD -> mainとorigin/mainが、同じパンを追加に並びました。新しいコミットは作られておらず、付箋が前に進んだだけです。これが fast-forward(早送り)です。$ git log --oneline --all 9ae3d51 (HEAD -> main, origin/main, origin/HEAD) パンを追加 c43e6a8 卵を追加 3f2a9c1 牛乳を追加up to dateに戻りました。今度は、origin/main を GitHub に合わせたうえでの「最新」です。$ git status On branch main Your branch is up to date with 'origin/main'. nothing to commit, working tree clean追いついたあとにもう一度打つと、
Already up to date.(もう最新です)とだけ出て、何も変わりません。$ git pull --ff-only Already up to date.
fetch と違って、pull は作業ツリーに触ります。ただし checkout と同じく、書きかけが上書きされるなら、何もせずに止まります。そのときは 1-7 stashで退避するか、コミットしてから pull し直します。
オプション無しの git pull は、早送りで済むときは同じですが、枝分かれしているときの動きが設定しだいです。pull.rebase false ならマージコミットを作り、pull.rebase true なら手元のコミットを作り直して origin/main の先に積み直します。どちらも設定していなければ、止まって「どうつなぐか決めて」と言います。--ff-only を付けておけば、歴史の形を変える操作は決してしません。
5. 2台で別々にコミットしたとき — diverged
fetch せずに作業を始めると、Windows と Mac で別々のコミットを積んでしまうことがあります。同じ親から2本に分かれた状態を、Git は diverged(枝分かれした)と呼びます。
2台で別々にコミットしたとき — diverged
Windows のノート PC で、GitHub の様子を確かめずに作業を始めます。
todo.txtに1行書き足します。$ echo "バターを買う" >> todo.txt手元の
mainがバターを追加に進みました。まだ push していないので、GitHub 側は卵を追加のままです。$ git commit -am "バターを追加" [main 15e1410] バターを追加 1 file changed, 1 insertion(+)同じころ、Mac でも別のコミット
メモを追加を作って push しました。GitHub 側のmainは、卵を追加から手元とは別の方向へ進みます。同じ親から2本に分かれた、枝分かれの状態です。ahead of 'origin/main' by 1 commit(1つ先に進んでいる)としか出ません。手元の origin/main はまだ卵を追加にあり、Mac の push を知らないからです。$ git status On branch main Your branch is ahead of 'origin/main' by 1 commit. (use "git push" to publish your local commits) nothing to commit, working tree cleanpush は拒否されます。
(fetch first)は「GitHub 側に、手元に無いコミットがある。先に fetch して」という意味です。GitHub 側のメモを追加を消してしまわないように、Git が止めています。$ git push To https://github.com/you/memo-app.git ! [rejected] main -> main (fetch first) error: failed to push some refs to 'https://github.com/you/memo-app.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.fetch すると
メモを追加が手元に届き、origin/main がそこへ進みます。下の手元のグラフでも、卵を追加から2本に分かれたのが見えるようになりました。$ git fetch From https://github.com/you/memo-app c43e6a8..9ae3d51 main -> origin/mainhave diverged(枝分かれした)と出ます。have 1 and 1 different commits eachは「手元のmainにだけあるコミットが1つ、origin/main にだけあるコミットが1つ」という意味です。$ git status On branch main Your branch and 'origin/main' have diverged, and have 1 and 1 different commits each, respectively. (use "git pull" if you want to integrate the remote branch with yours) nothing to commit, working tree cleangit branch -vvは、ブランチごとの上流と差を1行で見せます。[origin/main: ahead 1, behind 1]は「上流は origin/main で、1つ先に進み、1つ遅れている」という意味です。$ git branch -vv * main 15e1410 [origin/main: ahead 1, behind 1] バターを追加--ff-onlyは、付箋を前に進めるだけで済むときにしか動きません。枝分かれしているとNot possible to fast-forward, aborting.(早送りできないので中止します)と止まり、手元の main も作業ツリーも何も変えません。hint にあるgit mergeとgit rebaseが2本をつなぐ方法で、第3部で扱います。$ git pull --ff-only hint: Diverging branches can't be fast-forwarded, you need to either: hint: hint: git merge --no-ff hint: hint: or: hint: hint: git rebase hint: hint: Disable this message with "git config set advice.diverging false" fatal: Not possible to fast-forward, aborting.push も、今度は
(non-fast-forward)で拒否されます。手元のmainは GitHub 側のメモを追加を含んでいないので、そのまま送るとメモを追加が GitHub から消えてしまうからです。2本をつないでからでないと push できません。$ git push To https://github.com/you/memo-app.git ! [rejected] main -> main (non-fast-forward) error: failed to push some refs to 'https://github.com/you/memo-app.git' hint: Updates were rejected because the tip of your current branch is behind hint: its remote counterpart. If you want to integrate the remote changes, hint: use 'git pull' before pushing again. hint: See the 'Note about fast-forwards' in 'git push --help' for details.
この区間で止まった push・pull --ff-only・push は、どれもどちらかのコミットを消さないために止まっています。枝分かれは失敗ではなく、2台で並行して作業すれば普通に起きることです。2本をつなぐ方法は、両方の先端を親に持つマージコミットを作る git merge と、手元のコミットを origin/main の先に作り直す git rebase で、どちらも第3部で扱います。
やってはいけないのが、拒否された push を git push --force で押し通すことです。force push は GitHub の main を手元の位置に付け替えるので、Mac で push した メモを追加 が GitHub の main から外れます。Claude Code が --force を付けようとしたら、いったん止めて git fetch と git status で何がずれているかを見てください。
6. Claude Code が打つ fetch -q と、作業前の習慣
Claude Code が打った Git のコマンド延べ約14,000回のうち、git fetch は555回で、うち405回が git fetch -q でした。-q(quiet=静かに)は出力を消すだけで、origin/main の付箋はちゃんと動いています。pull は51回で、うち24回が git pull --ff-only です。「fetch で様子を見て、status で差を確かめ、早送りで済むなら pull --ff-only」という順番が、そのまま2台運用の習慣になります。
作業を始める前の習慣 — fetch・status・pull --ff-only
昨夜 Mac で push した分が、GitHub 側にあります。作業を始める前に、まず
git fetch -qで GitHub の様子を見に行きます。-q(quiet=静かに)は出力を消すだけで、origin/main の付箋はちゃんと進んでいます。$ git fetch -q次に
git statusで、origin/main との差を見ます。behindでcan be fast-forwardedなら、Mac で進めた分を取り込むだけで済みます。$ git status On branch main Your branch is behind 'origin/main' by 1 commit, and can be fast-forwarded. (use "git pull" to update your local branch) nothing to commit, working tree cleangit pull --ff-onlyで、手元のmainを origin/main まで進めます。fetch は済んでいるので、出力はUpdatingから始まります。$ git pull --ff-only Updating c43e6a8..9ae3d51 Fast-forward todo.txt | 1 + 1 file changed, 1 insertion(+)これで、GitHub と同じところから作業を始められます。
$ echo "バターを買う" >> todo.txtコミットすると、手元の
mainが origin/main の1つ先に進みます。$ git commit -am "バターを追加" [main 5f2309f] バターを追加 1 file changed, 1 insertion(+)ahead of 'origin/main' by 1 commitで、手元にだけコミットが1つあるのが分かります。最新に追いついてから積んだので、枝分かれはしていません。$ git status On branch main Your branch is ahead of 'origin/main' by 1 commit. (use "git push" to publish your local commits) nothing to commit, working tree cleanpush は拒否されずに通ります。GitHub 側の
mainと手元の origin/main が、そろってバターを追加へ進みます。$ git push To https://github.com/you/memo-app.git 9ae3d51..5f2309f main -> mainup to dateです。次に Mac で作業を始めるときも、fetch → status → pull --ff-only から始めれば、2台が枝分かれしません。$ git status On branch main Your branch is up to date with 'origin/main'. nothing to commit, working tree clean
作業を始める前は、git fetch → git status → behind なら git pull --ff-only の3手です。diverged なら、作業を始める前に2本をつなぎます。
この3手は「おかしいな」と思ったときにも効きます。ローカルと本番の食い違いを不具合と診断する前に、git fetch && git status で origin との差を見る、と決めておくと、未 pull のずれを不具合と取り違えずに済みます。Claude Code に任せるなら、プロジェクトの CLAUDE.md にこの1行を書いておくのも手です。
7. Claude Code の許可画面で見かけたら
この回で見たコマンドを、「何が消えうるか」で並べるとこうなります。
| コマンド | 何が起きるか | 消えうるもの |
|---|---|---|
git fetch / git fetch -q | origin/main などの追跡ブランチだけを動かす。手元の main・作業ツリーには触らない | なし |
git status / git branch -vv | 見るだけ。差は直前の fetch の時点と比べたもの | なし |
git pull --ff-only | fetch して、早送りで済むときだけ手元の main を進める。済まないときは何も変えずに止まる | なし(ただし作業ツリーのファイルが新しい中身になる) |
git pull(オプション無し) | fetch して、設定によってマージコミットを作る・rebase する・止まる | 注意 歴史の形が変わる(第3部) |
git push --force | GitHub のブランチを手元の位置に付け替える | もう1台の PC が push したコミット |
見分け方は、手元の main を進めるだけか、歴史の形を変えるかです。fetch と pull --ff-only は前者しかしません。
次の回では、GitHub の画面のブランチ・コミット・プルリクエストが、2つのグラフのどこに当たるかを対応づけます。マージボタンを押すと GitHub 側にだけマージコミットができ、それを手元に持ってくるのが、この回の git pull --ff-only です。
まとめ
- origin/main は PC ごとにある GitHub の写し。もう1台で push しても、こちらで fetch するまで動かない
git fetchは origin/main の付箋だけを動かす。手元のmainと作業ツリーには触らないので、いつ打っても安全。-qは出力を消すだけgit statusの up to date・ahead・behind・diverged は、直前の fetch の時点と比べた答え。git branch -vvでは[origin/main: ahead 1, behind 1]の形git pull --ff-onlyは fetch して、早送りで済むときだけmainを進める。枝分かれしていると何も変えずに止まる- 枝分かれすると push も止まる。つなぐのは merge か rebase(第3部)で、force push で押し通さない
- 作業の前は
git fetch→git status→ behind ならgit pull --ff-only