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 の mainGitHub に1枚どちらかの PC からの git push
origin/mainPC ごとに1枚その PC で打った git fetch / git pull / git push
mainPC ごとに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つです。

  1. GitHub に問い合わせて、手元に無いコミットを取ってくる
  2. 手元の origin/main を、GitHub の main の位置へ動かす

手元の main・HEAD・作業ツリー・ステージには触りません。Mac で push した直後の Windows のノート PC で、fetch の前と後を見比べます。

git fetch — GitHub の様子を見に行く

  1. もう1台の Mac で「パンを追加」をコミットして push しました。上の GitHub 側の main が1つ進みます。画面のターミナルは Windows のノート PC で、こちらにはまだ何も届いていません。

  2. 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 clean
  3. git 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
  4. 同じ 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
  5. --all を付けると、手元にあるすべての付箋が見えます。origin/main は パンを追加 に、HEAD -> main は 卵を追加 に貼ってあります。origin/HEAD は GitHub 側の既定のブランチを指す札で、ここでは気にしなくて構いません。

    $ git log --oneline --all
    9ae3d51 (origin/main, origin/HEAD) パンを追加
    c43e6a8 (HEAD -> main) 卵を追加
    3f2a9c1 牛乳を追加
  6. 作業ツリーの todo.txt は2行のままです。fetch は手元の main にも作業ツリーにも触らないので、書きかけがあっても、いつ打っても安全です。

    $ cat todo.txt
    牛乳を買う
    卵を買う
  7. もう一度 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/memoGitHub に新しいブランチがあったので、手元に 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 を追いつかせる

  1. Mac が push した直後の、fetch する前の状態から始めます。GitHub 側には パンを追加 があり、手元の todo.txt はまだ2行です。この動画では、下に作業ツリー・ステージ・HEAD のコミットの中身も出しています。

    $ cat todo.txt
    牛乳を買う
    卵を買う
  2. 出力は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(+)
  3. todo.txt に「パンを買う」が入りました。Mac で書いた1行が、Windows のノート PC に届いたことになります。

    $ cat todo.txt
    牛乳を買う
    卵を買う
    パンを買う
  4. HEAD -> main と origin/main が、同じ パンを追加 に並びました。新しいコミットは作られておらず、付箋が前に進んだだけです。これが fast-forward(早送り)です。

    $ git log --oneline --all
    9ae3d51 (HEAD -> main, origin/main, origin/HEAD) パンを追加
    c43e6a8 卵を追加
    3f2a9c1 牛乳を追加
  5. 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
  6. 追いついたあとにもう一度打つと、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

  1. Windows のノート PC で、GitHub の様子を確かめずに作業を始めます。todo.txt に1行書き足します。

    $ echo "バターを買う" >> todo.txt
  2. 手元の main が バターを追加 に進みました。まだ push していないので、GitHub 側は 卵を追加 のままです。

    $ git commit -am "バターを追加"
    [main 15e1410] バターを追加
     1 file changed, 1 insertion(+)
  3. 同じころ、Mac でも別のコミット メモを追加 を作って push しました。GitHub 側の main は、卵を追加 から手元とは別の方向へ進みます。同じ親から2本に分かれた、枝分かれの状態です。

  4. 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 clean
  5. push は拒否されます。(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.
  6. fetch すると メモを追加 が手元に届き、origin/main がそこへ進みます。下の手元のグラフでも、卵を追加 から2本に分かれたのが見えるようになりました。

    $ git fetch
    From https://github.com/you/memo-app
       c43e6a8..9ae3d51  main       -> origin/main
  7. have 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 clean
  8. git branch -vv は、ブランチごとの上流と差を1行で見せます。[origin/main: ahead 1, behind 1] は「上流は origin/main で、1つ先に進み、1つ遅れている」という意味です。

    $ git branch -vv
    * main 15e1410 [origin/main: ahead 1, behind 1] バターを追加
  9. --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.
  10. 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

  1. 昨夜 Mac で push した分が、GitHub 側にあります。作業を始める前に、まず git fetch -q で GitHub の様子を見に行きます。-q(quiet=静かに)は出力を消すだけで、origin/main の付箋はちゃんと進んでいます。

    $ git fetch -q
  2. 次に 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 clean
  3. git 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(+)
  4. これで、GitHub と同じところから作業を始められます。

    $ echo "バターを買う" >> todo.txt
  5. コミットすると、手元の main が origin/main の1つ先に進みます。

    $ git commit -am "バターを追加"
    [main 5f2309f] バターを追加
     1 file changed, 1 insertion(+)
  6. 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 clean
  7. push は拒否されずに通ります。GitHub 側の main と手元の origin/main が、そろって バターを追加 へ進みます。

    $ git push
    To https://github.com/you/memo-app.git
       9ae3d51..5f2309f  main -> main
  8. up 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 -qorigin/main などの追跡ブランチだけを動かす。手元の main・作業ツリーには触らないなし
git status / git branch -vv見るだけ。差は直前の fetch の時点と比べたものなし
git pull --ff-onlyfetch して、早送りで済むときだけ手元の main を進める。済まないときは何も変えずに止まるなし(ただし作業ツリーのファイルが新しい中身になる)
git pull(オプション無し)fetch して、設定によってマージコミットを作る・rebase する・止まる注意 歴史の形が変わる(第3部)
git push --forceGitHub のブランチを手元の位置に付け替えるもう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