git clone・pull・fetchの違い:受け取る操作の使い分け

公開 更新 書いた人 大林 諒

GitHubから受け取るコマンドは3つあり、名前が似ているので混ざります。git clone、git fetch、git pull です。

この記事では、サンプル料理教室の代表の手元と外注エンジニアBの手元を実際に作り、Bが送った変更を代表が受け取るところまでを動かします。読み終わると、どの場面でどれを打てばよいかが分かり、pullが止まったときの英語も読めるようになります。

この記事の内容
  1. 先に答えると
  2. 3つの違い
  3. forkはcloneと何が違うか
  4. 代表とBの手元を作って動かす
  5. 作業を始める前にpullする
  6. pullで起きる4つのこと
  7. できないことと、その代わり
  8. よく詰まるところ
  9. 次に読む

先に答えると

cloneは最初の1回だけ打つ丸ごとの複製です。fetchはGitHubの新しい変更を確認するだけで、手元のファイルは変わりません。pullはfetchをしたうえで手元に反映します。ふだん使うのはpullで、cloneは最初だけ、fetchは反映する前に中身を見たいときに打ちます。

  • 手元にまだ何もないなら git clone。
  • 手元にあって、最新にしたいなら git pull。
  • 反映せずに、何が来ているか見たいだけなら git fetch。
  • forkはGitHubの中での複製で、cloneとは別の話です。自社のリポジトリに招待されているなら、forkは要りません。
  • Mac1台で一人で使っているなら、pullの出番は多くありません。

3つの違い

3つの違いは、手元の何が変わるかで見ると分かります。

コマンドいつ打つか手元のファイル手元の履歴
git clone最初の1回新しいフォルダに全部できる全部の履歴が入る
git fetch中身を見てから反映したいとき変わらないGitHub側の控えだけ更新される
git pull作業を始める前など、ふだん最新になる手元の枝(ブランチ)まで更新される

レシピの束に例えると、cloneはレシピ帳を丸ごと1冊コピーすること、fetchは新しいレシピの封筒を受け取って机に置くこと、pullは封筒を開けてレシピ帳に綴じることです。Git公式の説明でも、git fetch はデータを取ってくるだけでマージはしないとされ、git pull はfetchと合流を続けて行うものとされています。

clone・fetch・pull がそれぞれ何を変えるか
clone・fetch・pull がそれぞれ何を変えるか

forkはcloneと何が違うか

fork(フォーク)は、GitHubの中で他の人のリポジトリを自分のアカウントかOrganizationへ複製する機能です。cloneは手元のパソコンへ複製します。

forkclone
複製先GitHub上の自分のアカウントかOrganization手元のパソコン
使う場面書き込み権限のないリポジトリに手を加えて提案したい手元でファイルを編集したい
操作する場所GitHubの画面コマンドや手元の道具

サンプル料理教室の website のように、Organizationの中で招待されて権限があるリポジトリなら、forkは使いません。cloneして、枝(ブランチ。履歴の流れの1本)を作って送れば済みます。forkの出番は、誰かが公開しているツールを自分用に直したいときくらいです。

代表とBの手元を作って動かす

ここからの表示は、すべて実際に動かしたものです。GitHubの代わりに、手元のフォルダに練習用のリモート(共有の置き場となるリポジトリ)を作り、代表用とB用のフォルダにそれぞれ複製しました。そのため送り先の場所がフォルダの形で出ます。長いフォルダ名は … に縮めました。本物のGitHubなら https://github.com/sample-cooking/website.git のようなURLになります。

cloneで複製する

$ git clone file://…/remote/website.git
Cloning into 'website'...
remote: Enumerating objects: 5, done.
remote: Counting objects: 100% (5/5), done.
remote: Compressing objects: 100% (4/4), done.
remote: Total 5 (delta 0), reused 0 (delta 0), pack-reused 0 (from 0)
Receiving objects: 100% (5/5), done.

website というフォルダができます。Gitで動かすときは、そのフォルダの中へ移動します。中を見ると、ファイルと一緒に履歴の入った .git フォルダがあります。

$ ls -a
.		.git		menu.html
..		index.html	style.css

$ git remote -v
origin	file://…/remote/website.git (fetch)
origin	file://…/remote/website.git (push)

$ git log --oneline
58768ea (HEAD -> main, origin/main, origin/HEAD) ホームページの最初の版を登録

HEAD -> main は今いる枝が main だという印です。origin は、複製元を指す名前です。cloneすると自動で付くので、自分で登録する必要はありません。origin/main は、GitHub側の main が最後に確かめたときどこにいたかの控えです。手元の main とは別に持っています。

Bが送る

Bが自分の手元で料金表を直して、pushします。

$ git commit -m "料金表を修正"
[main d3a2f25] 料金表を修正
 1 file changed, 1 insertion(+), 1 deletion(-)

$ git push
Enumerating objects: 5, done.
Counting objects: 100% (5/5), done.
Delta compression using up to 11 threads
Compressing objects: 100% (3/3), done.
Writing objects: 100% (3/3), 386 bytes | 386.00 KiB/s, done.
Total 3 (delta 1), reused 0 (delta 0), pack-reused 0 (from 0)
To file://…/remote/website.git
   58768ea..d3a2f25  main -> main

代表が確かめる。statusだけでは分からない

この直後に、代表が自分の手元で git status を打ちます。

$ git status
On branch main
Your branch is up to date with 'origin/main'.

nothing to commit, working tree clean

Bが送った直後なのに「up to date(最新です)」と出ました。ここがいちばんの落とし穴です。git status はGitHubに確かめに行きません。手元にある控えと比べているだけです。

fetchで確かめる

$ git fetch
remote: Enumerating objects: 5, done.
remote: Counting objects: 100% (5/5), done.
remote: Compressing objects: 100% (3/3), done.
remote: Total 3 (delta 1), reused 0 (delta 0), pack-reused 0 (from 0)
Unpacking objects: 100% (3/3), 366 bytes | 183.00 KiB/s, done.
From file://…/remote/website
   58768ea..d3a2f25  main       -> origin/main

$ 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

「behind ‘origin/main’ by 1 commit」は、手元の main がGitHubより1つ遅れているという意味です。「can be fast-forwarded」は、手元には新しいコミットがないので、先頭を進めるだけで追いつけるという意味です。

fetchではファイルは変わっていません。料金表はまだ3,000円のままです。

$ grep 体験 menu.html
<p>体験レッスン 3,000円</p>

反映する前に、何が来たかを見られます。

$ git log --oneline main..origin/main
d3a2f25 (origin/main, origin/HEAD) 料金表を修正

$ git diff main origin/main
diff --git a/menu.html b/menu.html
index 27bcd2a..d7374cb 100644
--- a/menu.html
+++ b/menu.html
@@ -1,5 +1,5 @@
 <h1>料金表</h1>
-<p>体験レッスン 3,000円</p>
+<p>体験レッスン 3,500円</p>
 <p>月謝 12,000円</p>
 <p>道具代 500円</p>
 <p>入会金 5,000円</p>

main..origin/main は、「手元の main になくて、origin/main にあるもの」の指定です。- が今の手元、+ が受け取る内容です。

fetchの直後とpullのあとの違い
fetchの直後とpullのあとの違い

pullで反映する

$ git pull
Updating 58768ea..d3a2f25
Fast-forward
 menu.html | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

$ grep 体験 menu.html
<p>体験レッスン 3,500円</p>

$ git log --oneline
d3a2f25 (HEAD -> main, origin/main, origin/HEAD) 料金表を修正
58768ea ホームページの最初の版を登録

$ git pull
Already up to date.

fetch済みなので、今回は受信の行が出ずに反映だけが走りました。ふつうは先にfetchせず、git pull だけを打ちます。その場合は受信の行のあとに、同じ表示が続きます。「Already up to date.」は、もう受け取るものがないという表示です。

作業を始める前にpullする

実際に運用していて、事故がいちばん減るのは、ファイルを開く前に git pull を打つ習慣です。Bが直した古い料金表の上から代表が編集を始めると、後で合流させる手間が増えます。先に受け取っておけば、ほとんどの場合、次の章で出てくるエラーに会いません。

一方で、Mac1台で一人だけが触っているリポジトリなら、受け取るものが発生しないのでpullの出番は多くありません。それでも要るのは次のようなときです。

  • GitHubの画面でファイルを直接編集したとき
  • 2台目のパソコンでも作業しているとき
  • 外注エンジニアBが同じリポジトリにpushするとき

pullで起きる4つのこと

pullの結果は、手元の状態で4通りに分かれます。

手元の状態起きること表示
手元に新しいコミットがない先頭を進めるだけFast-forward
未コミットの編集がぶつかる止まる。手元のファイルは変わらないYour local changes ... would be overwritten
お互いにコミットがある指定がないと止まるdivergent branches
お互いのコミットを合流する指定ありマージするMerge made by the 'ort' strategy.

fast-forwardは、先ほど見たとおりです。残りの3つを順に再現します。

未コミットの編集があると止まる

代表がキャンセル料を編集している途中で、Bが月謝を直してpushしました。同じ menu.html です。

$ git pull
remote: Enumerating objects: 5, done.
remote: Counting objects: 100% (5/5), done.
remote: Compressing objects: 100% (3/3), done.
remote: Total 3 (delta 1), reused 0 (delta 0), pack-reused 0 (from 0)
Unpacking objects: 100% (3/3), 364 bytes | 182.00 KiB/s, done.
From file://…/remote/website
   d3a2f25..bfd8b28  main       -> origin/main
Updating d3a2f25..bfd8b28
error: Your local changes to the following files would be overwritten by merge:
	menu.html
Please commit your changes or stash them before you merge.
Aborting

「このまま反映すると、手元の編集が上書きされるので止めた」という意味です。編集は消えていません。直し方は2つあります。編集が区切りのよいところまで進んでいるなら、先にコミットします。途中なら、git stash で一時的によけて、pullしてから戻します。

$ git stash
Saved working directory and index state WIP on main: d3a2f25 料金表を修正

$ git pull
Updating d3a2f25..bfd8b28
Fast-forward
 menu.html | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

$ git stash pop
Auto-merging menu.html
On branch main
Your branch is up to date with 'origin/main'.

Changes not staged for commit:
  (use "git add <file>..." to update what will be committed)
  (use "git restore <file>..." to discard changes in working directory)
	modified:   menu.html

no changes added to commit (use "git add" and/or "git commit -a")
Dropped refs/stash@{0} (0dbf1b4d6e02b0b67ebce5a8d17b29d4e6551b62)

Bの月謝の修正と、代表のキャンセル料の編集が、両方入った状態に戻りました。

お互いがコミットしていると止まる

代表が「夏の教室を追加」をコミットしたところへ、Bが先に「見出しの色を修正」をpushしていました。fetchして見ると、枝分かれしています。

$ 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

$ git log --oneline --graph --all
* 5cf712d (HEAD -> main) 夏の教室を追加
| * cab72ff (origin/main, origin/HEAD) 見出しの色を修正
|/  
* e9e7eeb キャンセル料を修正
* bfd8b28 月謝を修正
* d3a2f25 料金表を修正
* 58768ea ホームページの最初の版を登録

この状態で git pull を打つと、Gitは止まります。

$ git pull
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
hint:
hint: You can replace "git config" with "git config --global" to set a default
hint: preference for all repositories. You can also pass --rebase, --no-rebase,
hint: or --ff-only on the command line to override the configured default per
hint: invocation.
fatal: Need to specify how to reconcile divergent branches.

長いですが、言っているのは「枝分かれしているので、合流のやり方を指定してください」だけです。

マージで合流する

最初のうちは、マージ(2つの流れを1つに合わせること)を選ぶのが分かりやすいです。--no-rebase がその指定です。合流のメッセージを書くエディタが開いたら、既定の文言のまま保存して閉じます。

$ git pull --no-rebase
Merge made by the 'ort' strategy.
 style.css | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

$ git log --oneline --graph
*   4e3586a (HEAD -> main) Merge branch 'main' of file://…/remote/website
|\  
| * cab72ff (origin/main, origin/HEAD) 見出しの色を修正
* | 5cf712d 夏の教室を追加
|/  
* e9e7eeb キャンセル料を修正
* bfd8b28 月謝を修正
* d3a2f25 料金表を修正
* 58768ea ホームページの最初の版を登録

このあと git push を打てば、合流した結果がGitHubへ届きます。毎回指定するのが面倒なら、リポジトリごとに git config --local pull.rebase false と設定しておくと、次からは git pull だけでマージまで進みます。

git pull –rebase は最初は使わなくていい

エラーに出てくるもう1つの選択肢がrebase(リベース)です。git pull --rebase は、自分のコミットをいったん外して、受け取ったコミットの後ろに付け直します。履歴が枝分かれせずに1本で並ぶので、好む人もいます。

ただ、コミットを作り直す操作なので、途中で止まったときの扱いが少し難しくなります。小さな会社で最初から使う必要はありません。外注エンジニアBのリポジトリで「rebaseで揃えてください」と言われたときに、調べれば足ります。

mergeとrebaseの履歴の形の違い
mergeとrebaseの履歴の形の違い

できないことと、その代わり

できないこと代わりにどうするか
fetchだけで手元のファイルを更新するgit pull を打つ
git status だけでGitHubの最新を知る先に git fetch を打つ
「Download ZIP」で履歴ごと受け取るZIPは履歴を含まない。履歴も要るならcloneする
招待されていない非公開リポジトリをcloneする管理者に招待してもらう
GitHubのアカウントがあるだけで手元からcloneする非公開リポジトリは認証の設定が要る。「Gitのインストールと初期設定:MacとWindows、名前とメールと認証」を先に

よく詰まるところ

destination path already exists と出る

$ git clone file://…/remote/website.git
fatal: destination path 'website' already exists and is not an empty directory.

同じ名前のフォルダがすでにあります。もう一度cloneする必要はほとんどなく、そのフォルダに入って git pull を打てば最新になります。

not a git repository と出る

$ git status
fatal: not a git repository (or any of the parent directories): .git

原因は、リポジトリの外にいることです。cloneで website ができたあと、その中へ移動していないときによく出ます。cd website で入ってから打ち直します。

Repository not found と出る

GitHubの公式ページによると、リポジトリが存在しないか、見る権限がないときに出る表示です。URLの打ち間違いを避けるため、リポジトリの画面からコピーします。非公開リポジトリなら、招待を受けているかを担当者に確かめます。

There is no tracking information と出る

作ったばかりの枝で git pull を打つと出ます。

$ git pull
There is no tracking information for the current branch.
Please specify which branch you want to merge with.
See git-pull(1) for details.

    git pull <remote> <branch>

If you wish to set tracking information for this branch you can do so with:

    git branch --set-upstream-to=origin/<branch> add-summer-class

この枝にはまだ追いかける先がないので、どこから受け取ればよいか分からないという意味です。その枝をまだ誰も送っていないなら、受け取るものはありません。git push -u origin add-summer-class で一度送れば、次からは git pull が通ります。

Bが送ったと言うのに Already up to date と出る

多くの場合、Bが送ったのが別の枝です。git fetch のあとに git branch -a を打つと、GitHub側の枝も一覧で見られます。remotes/origin/ で始まる行が、GitHub側の枝の控えです。その枝をBが何という名前で送ったかを聞いて、git switch 枝の名前 で移ります。

pullしたら CONFLICT と出た

お互いが同じ行を変えていると、Gitは自動で合流できません。

$ git pull --no-rebase
Auto-merging menu.html
CONFLICT (content): Merge conflict in menu.html
Automatic merge failed; fix conflicts and then commit the result.

実際の表示では、この前に受信の行が並びます。壊れたわけではありません。やめて元に戻したいなら git merge --abort を打つと、pullの前の状態に戻ります。直し方は「コンフリクトの直し方:起きる理由と、解消の手順」で扱います。

pullで競合したら、git merge --abort で元に戻せる
pullで競合したら、git merge –abort で元に戻せる

次に読む

送る側の話は「git add・commit・pushの違い:保存して送るまでの流れ」にあります。枝分かれと合流をきちんと理解するなら「git branchとmergeの使い方:枝を分けて合流する、GitHub Flowまで」へ。

同じ行がぶつかったときは「コンフリクトの直し方:起きる理由と、解消の手順」、cloneの前の準備は「Gitのインストールと初期設定:MacとWindows、名前とメールと認証」に書いています。

あわせて読む