Git入門:バージョン管理のきほん

clone と fetch の違い

生田 陸人
LuaGate エンジニア / 現役エンジニア
編集 LuaGate編集部

このレッスンで分かること

  • clone が「まるごと複製 + リモート登録 + 初回チェックアウト」をまとめてやっていること
  • origin/main というリモート追跡ブランチが何を記録しているか
  • fetch は取ってくるだけで手元に触らない、という境界

clone がやっている 3 つのこと

他人のリポジトリや、GitHub 上に作った自分のリポジトリを手元に持ってくるとき、最初に打つのが git clone です。

ターミナル

$ git clone git@github.com:example/myapp.git Cloning into 'myapp'... remote: Enumerating objects: 142, done. remote: Counting objects: 100% (142/142), done. remote: Compressing objects: 100% (89/89), done. Receiving objects: 100% (142/142), 24.31 KiB | 4.86 MiB/s, done. Resolving deltas: 100% (48/48), done.

1 行のコマンドですが、内部では 3 つのことが順に行われています。

  1. myapp というディレクトリを作り、Git リポジトリとして初期化する
  2. リモートの全履歴をダウンロードし、そのリモートに origin という別名を登録する
  3. 既定のブランチ (多くの場合 main) をチェックアウトして、作業ファイルを並べる

3 番まで済んでいるので、clone した直後からファイルが見える状態になっています。

確認してみます。

ターミナル

$ cd myapp $ git remote -v origin git@github.com:example/myapp.git (fetch) origin git@github.com:example/myapp.git (push) $ git log --oneline -3 8a2f4e6 (HEAD -> main, origin/main, origin/HEAD) 検索 API のレスポンスを整形 3b9c0d1 ユーザー一覧のページング 1a2b3c4 プロジェクトの初期設定

origin が自動で登録されている点に注目してください。前のレッスンで git remote add を手で打ったのは、git init で自分で作ったリポジトリの場合です。clone した場合は不要です。

clone は最初の 1 回だけです。 「リポジトリを更新したい」と思って再び clone すると、別のディレクトリにまったく別のコピーができます。手元の変更も設定も引き継がれません。2 回目以降は fetch か pull を使います。

clone のときに指定できること

そのままでは、URL の末尾がディレクトリ名になります。別の名前にしたければ、URL のあとに書きます。

ターミナル

$ git clone git@github.com:example/myapp.git my-work Cloning into 'my-work'...

既定以外のブランチをチェックアウトした状態で始めたいときは -b を使います。

ターミナル

$ git clone -b develop git@github.com:example/myapp.git

履歴が長いリポジトリで、最新の状態だけあれば足りる場合は --depth 1 をつけると、ダウンロード量が大きく減ります。

ターミナル

$ git clone --depth 1 git@github.com:example/myapp.git

ただしこれは過去のコミットを持ってこないので、git log はほぼ何も表示せず、古いコミットとの比較もできません。自動テストの実行環境など、履歴が要らない用途に限って使ってください。

origin/main は何者か

さきほどの git log の出力に、見慣れない名前がありました。

プレーンテキスト

8a2f4e6 (HEAD -> main, origin/main, origin/HEAD) 検索 API のレスポンスを整形

mainorigin/main の 2 つが同じコミットを指しています。この 2 つは別物です。

origin/main はリモート追跡ブランチと呼ばれます。役割は 1 つで、最後にリモートと通信したとき、リモートの main がどのコミットを指していたかを記録することです。

名前何を指すか誰が動かすか
main手元の作業ブランチ自分がコミットしたとき進む
origin/main最後に見たリモートの main の位置fetch や pull をしたときだけ進む

大事なのは、origin/mainリモートを見にいっているわけではないという点です。ネットワークの向こうを覗く窓ではありません。手元のディスクに保存された、前回の通信結果のメモです。

だから、あなたがオフラインでも origin/main は読めます。そして、他の人が GitHub に push しても、あなたが fetch するまで origin/main は動きません。表示されている情報が古い可能性は常にあります。

一覧してみると、手元のブランチとリモート追跡ブランチが分かれて並びます。

ターミナル

$ git branch * main feature-login $ git branch -r origin/HEAD -> origin/main origin/main origin/feature-search $ git branch -a * main feature-login remotes/origin/HEAD -> origin/main remotes/origin/main remotes/origin/feature-search

-r がリモート追跡ブランチだけ、-a が両方です。ここで origin/feature-search が見えているのは、誰かが GitHub 上でそのブランチを作り、あなたがそれを fetch したからです。

他の人が作ったブランチで作業したくなったら、その名前でブランチを作ります。

ターミナル

$ git switch feature-search branch 'feature-search' set up to track 'origin/feature-search'. Switched to a new branch 'feature-search'

手元に無いブランチ名を git switch に渡すと、同じ名前のリモート追跡ブランチを探して、それを元にした作業ブランチを自動で作ってくれます。上流ブランチの設定まで済むので、以後は引数なしの push と pull が使えます。

リモート追跡ブランチは読み取り専用です。origin/main に直接コミットすることはできません。切り替えようとすると detached HEAD になります。あくまで記録用の目印だと考えてください。

fetch は取ってくるだけ

ここがこのレッスンの核心です。

ターミナル

$ git fetch remote: Enumerating objects: 9, done. remote: Counting objects: 100% (9/9), done. Unpacking objects: 100% (5/5), 638 bytes | 638.00 KiB/s, done. From github.com:example/myapp 8a2f4e6..c7d1b93 main -> origin/main

最後の行が起きたことのすべてです。origin/main8a2f4e6 から c7d1b93 に進みました。

そして、それ以外は何も変わっていません。

  • 手元の main8a2f4e6 のままです
  • 作業中のファイルは 1 文字も書き換わっていません
  • 編集途中のファイルがあっても、fetch は文句を言いません

確認します。

ターミナル

$ 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

Git が「リモートのほうが 1 コミット進んでいます。取り込むなら pull してください」と教えてくれています。取り込むかどうかは、まだあなたが決めていない状態です。

前のレッスンで見た通り、git pull はこの fetch に続けて merge まで実行します。境界はこうです。

コマンドリモートの情報を取ってくる手元のブランチを進める作業ファイルを書き換える
git fetchするしないしない
git pullするするする

fetch は取ってくるだけ、pull は取ってきて合流まで。 この一文を覚えておけば、どちらを打つべきか迷いません。

fetch してから中身を見る

fetch の価値は、取り込む前に相手の変更を確認できることにあります。

まず、差を見ます。

ターミナル

$ git log --oneline main..origin/main c7d1b93 パスワードの最小文字数を 12 に変更

main..origin/main は「main には無くて origin/main にあるコミット」という指定です。これから降ってくるものが分かります。

中身も見られます。

ターミナル

$ git diff main origin/main diff --git a/src/validators/password.js b/src/validators/password.js index 4a8c1e2..9f3b7d0 100644 --- a/src/validators/password.js +++ b/src/validators/password.js @@ -1,5 +1,5 @@ export function validatePassword(value) { - if (value.length < 8) { + if (value.length < 12) { return "パスワードが短すぎます"; }

自分がいまその同じファイルを編集していると分かれば、pull する前に手を打てます。コミットしてから pull する、あるいは stash で退避してから pull する、という判断ができます。

いきなり pull すると、この判断の機会がありません。コンフリクトの解決作業に突然放り込まれます。

長く作業していない日の朝は、いきなり git pull するより git fetch を打って git log main..origin/main を眺めるほうが安全です。何が変わったかを知ってから取り込むだけで、事故はかなり減ります。

リモートで消えたブランチを掃除する

しばらく開発を続けると、git branch -r の一覧がどんどん増えていきます。マージ済みで GitHub 上からは削除されたブランチが、手元には残り続けるからです。

fetch は新しい情報を追加しますが、消えたものを勝手には消しません。掃除するには明示します。

ターミナル

$ git fetch --prune From github.com:example/myapp - [deleted] (none) -> origin/feature-search

毎回つけたければ設定できます。

ターミナル

$ git config --global fetch.prune true

--prune が消すのは origin/feature-search というリモート追跡ブランチだけです。手元に feature-search という作業ブランチを持っていれば、それはそのまま残ります。安全な操作です。

やりがちな失敗

いちばん多いのは、fetch だけして取り込んだつもりになることです。

ターミナル

$ git fetch From github.com:example/myapp 8a2f4e6..c7d1b93 main -> origin/main

この出力を見て「取ってきた」と思い、そのまま作業を続ける。しかし手元の main は動いていないので、他の人の修正は反映されていません。数時間後に push しようとして弾かれ、大きくなった差分を解決するはめになります。

fetch の後に、必ず git status を打つ習慣をつけてください。behind と出ていれば、まだ取り込んでいません。

もうひとつは、clone を更新手段だと思う失敗です。「最新にしたいから clone し直す」というやり方は、手元のコミットしていない作業を丸ごと置き去りにします。しかも古いディレクトリが残るので、どちらで作業していたのか分からなくなります。手元にリポジトリがあるなら、更新は必ず fetch か pull です。

やりたいこと使うもの
リポジトリを初めて手元に持ってくるgit clone
リモートに何があるか知りたいgit fetch
リモートの変更を自分のブランチに入れたいgit pull
消えたリモートブランチの記録を掃除するgit fetch --prune
この章のポイント
  • clone は複製とリモート登録と初回チェックアウトをまとめてやる。使うのは最初の 1 回だけ
  • origin/main は最後に通信したときのリモートの位置を記録した読み取り専用の目印で、リアルタイムのリモートではない
  • fetch は取ってくるだけで、手元のブランチにも作業ファイルにも触らない。pull は合流までやる
  • fetch のあとは git statusgit log main..origin/main で中身を確かめてから取り込む