Claude Codeのgit操作が何をしているか分かれば、AIとのチーム開発はもっとうまくいく
はじめに: Claude Codeはgitを「使いこなしている」
Claude Codeを使っていると、コードの変更だけでなく、gitの操作まで自動でやってくれることに気づきます。
- ブランチを切る
- こまめにcommitする
- pushしてリモートに同期する
私の場合はPRを作るのも面倒なので、gh CLIを使えるようにして、ブランチ作成からPR作成まで全部Claude Codeに任せています。便利ですよね。
でも、こんな経験はありませんか?
「Claude Codeがやってくれたけど、何が起きたのかよく分からない…」
gitの仕組みを理解していると、Claude Codeへの指示の出し方が変わります。「mainにpushして」と「featureブランチ切ってPR出して」では、裏で起きることがまったく違います。
この記事では、Claude Codeが裏側で実行しているgit操作を一つずつ分解して、仕組みから理解することを目指します。各セクションでは「本・図書館・しおり」の世界観で例えてから技術的な説明に入るので、gitに苦手意識がある方もイメージを掴みやすいはずです。
git fetchとgit pull — リモートとローカルの関係
本で例えると: fetchは「図書館の蔵書目録を確認する」操作。pullは「目録を確認して、さらに本を借りてくる」操作です。目録を見るだけなら手元の本は変わりませんよね。
fetchとpullは何が違うのか
gitには「リモートリポジトリ」と「ローカルリポジトリ」の2つの世界があります。この2つの間でデータをやり取りするのがfetchとpullです。
| コマンド | やること | ローカルのコードは変わる? |
|---|---|---|
git fetch |
リモートの最新情報を取得するだけ | ❌ 変わらない |
git pull |
fetch + merge(取得して統合する) |
✅ 変わる |
# fetchは「見に行くだけ」
$ git fetch origin
# pullは「見に行って、自分のコードに取り込む」
$ git pull origin main
.git/refs の中身を覗いてみよう
gitの内部では、リモートとローカルの情報は別々の場所に保存されています。
.git/refs/
├── heads/ # ← ローカルブランチ
│ ├── main
│ └── feature/add-login
└── remotes/
└── origin/ # ← リモート追跡ブランチ
├── main
└── feature/add-login
-
refs/heads/main→ あなたのローカルのmainが指すコミット -
refs/remotes/origin/main→ リモートのmainが最後にfetchしたときに指していたコミット
fetchは refs/remotes/origin/ を更新するだけ。refs/heads/ には触りません。
Claude Codeが「まずfetchしてからpullする」理由
Claude Codeはタスクを開始するとき、まずgit fetchを実行してリモートの状態を確認します。これは「いきなりpullしてコンフリクトが起きる」ことを避けるためです。
fetchで安全に状態を把握してから、必要に応じてpullする。この2段階の確認が、安全な操作の基本です。
図解: fetchとpullの違い
git branchとgit switch — ブランチの正体
身近なもので例えると: ブランチは本のしおりのようなもの。本(コード)はコピーされず、どのページを読んでいるかの目印を増やすだけです。switchはしおりを挟み替える操作。しおりを何枚増やしても本は重くなりませんよね。
ブランチは「ポインタ」でしかない
「ブランチ」と聞くと、コードが枝分かれしたコピーのようなイメージがあるかもしれません。しかし、gitのブランチの正体は1つのコミットを指すポインタです。
実際に中身を見てみましょう。
$ cat .git/refs/heads/main
a1b2c3d4e5f6789012345678901234567890abcd
たったこれだけ。40文字のSHA-1ハッシュが書かれているだけです。このハッシュが「このブランチはどのコミットを指しているか」を表しています。
switchはHEADの向き先を変えるだけ
git switch(旧git checkout)がやっていることは、HEADというポインタの向き先を変えることです。
# HEADの向き先を確認
$ cat .git/HEAD
ref: refs/heads/main
# ブランチを切り替える
$ git switch feature/add-login
# HEADが変わった
$ cat .git/HEAD
ref: refs/heads/feature/add-login
ファイルが切り替わるのは、HEADが指すコミットに合わせてワーキングツリーを更新するからです。
Claude Codeがfeatureブランチを切るとき
Claude Codeに「この機能を実装して」と指示すると、多くの場合こんな操作が走ります。
# 1. 新しいブランチを作成して切り替え
$ git switch -c feature/add-login
# これは内部的には2つのことが起きている:
# ① .git/refs/heads/feature/add-login を作成(現在のコミットを指す)
# ② .git/HEAD を refs/heads/feature/add-login に向ける
ブランチの作成は「新しいポインタを1つ作る」だけなので、コストはほぼゼロです。そのため、Claude Codeは気軽にブランチを作成できます。
detached HEADの正体
たまに見かける「detached HEAD」状態。これは、HEADがブランチではなくコミットを直接指している状態です。
# 通常: HEADはブランチを指す
$ cat .git/HEAD
ref: refs/heads/main
# detached HEAD: HEADがコミットを直接指す
$ cat .git/HEAD
a1b2c3d4e5f6789012345678901234567890abcd
この状態でコミットしても、どのブランチにも属さないため、後から見つけにくくなります。Claude Codeがブランチを必ず作るのは、この問題を避けるためでもあります。
図解: ブランチとHEADの関係
git commit — スナップショットの仕組み
本で例えると: commitは「本の全ページをコピー機でコピーして、スクラップノートに貼り付ける」操作です。変わったページだけメモするのではなく、その時点の全体をまるごとコピーする。後から「あのときどうだったっけ」と振り返れるのは、全ページのコピーが残っているからです。
commitは差分ではなく「スナップショット」
よくある誤解に「commitは変更の差分を保存している」というものがあります。実際には、gitのcommitはプロジェクト全体のスナップショットを保存しています。
# コミットの中身を見てみる
$ git cat-file -p HEAD
tree 9a8b7c6d5e4f3a2b1c0d9e8f7a6b5c4d3e2f1a0b
parent a1b2c3d4e5f6789012345678901234567890abcd
author Developer <dev@example.com> 1710000000 +0900
committer Developer <dev@example.com> 1710000000 +0900
feat: ログイン機能を追加
.git/objectsの3層構造
gitのオブジェクトは3種類あります。
| オブジェクト | 役割 | 例え |
|---|---|---|
| blob | ファイルの中身 | 写真1枚1枚 |
| tree | ディレクトリ構造 | アルバムの目次 |
| commit | メタデータ(誰が・いつ・何を) | アルバムの表紙 |
# commitオブジェクト → treeを指す
$ git cat-file -p HEAD
tree 9a8b7c6d...
# treeオブジェクト → blobやサブtreeを指す
$ git cat-file -p 9a8b7c6d
100644 blob 1234abcd... README.md
100644 blob 5678efgh... src/index.ts
040000 tree 9012ijkl... src/components/
# blobオブジェクト → ファイルの中身そのもの
$ git cat-file -p 1234abcd
# Hello World
This is a sample project.
Claude Codeの「こまめなcommit」が安全な理由
Claude Codeは作業の途中でこまめにcommitします。「まだ途中なのにcommitするの?」と思うかもしれませんが、これには大きなメリットがあります。
commitはスナップショットなので、いつでもその時点に戻れます。
# もし何か問題が起きても、直前のコミットに戻れる
$ git log --oneline
d4e5f6a feat: バリデーション追加
c3d4e5f feat: フォーム実装
b2c3d4e feat: コンポーネント作成
a1b2c3d feat: 初期セットアップ
# 例えば「フォーム実装」の時点に戻したいなら
$ git reset --hard c3d4e5f
こまめなcommitは「セーブポイント」のようなものです。ゲームでこまめにセーブするのと同じ感覚ですね。
図解: gitオブジェクトの3層構造
git push — ローカルとリモートの同期
本で例えると: pushは「読書ノートを共有本棚に戻す」操作です。自分の手元にもノートは残りますが、共有本棚に置くことでチームの誰もが見られるようになります。戻すタイミングは自分で決められる。この「手動」が安全性の源です。
pushは「refsをリモートにコピーする」操作
git pushは、ローカルのブランチ(refs)が指すコミットと、そのコミットに必要なオブジェクトをリモートに送信します。
$ git push origin feature/add-login
# やっていること:
# 1. feature/add-login が指すコミット(とそこに至るオブジェクト)を送信
# 2. リモートの refs/heads/feature/add-login を更新
fast-forwardとnon-fast-forwardの違い
pushが成功するかどうかは、リモートのブランチとローカルのブランチの履歴の関係で決まります。
fast-forward(成功する):
リモートのブランチが指すコミットが、ローカルのブランチの祖先にある場合。つまり、リモートの履歴をそのまま「早送り」できる場合です。
リモート: A → B → C
ローカル: A → B → C → D → E
↑ この分だけ追加すればOK
non-fast-forward(拒否される):
リモートとローカルで履歴が分岐している場合。
リモート: A → B → C → F
ローカル: A → B → C → D → E
↑ ここで分岐している
Claude Codeがpushを拒否されるケース
Claude Codeがpushしようとして拒否されるのは、主に以下のケースです。
- 他の人が先にpushした — リモートが進んでいるため、fast-forwardできない
- mainブランチに直接pushしようとした — ブランチ保護ルールで拒否される
- force pushが必要な状況 — rebaseした後など、履歴が書き換わっている
# 拒否された場合の対処
$ git push origin feature/add-login
! [rejected] feature/add-login -> feature/add-login (non-fast-forward)
# 対処法1: まずpullしてmergeする
$ git pull origin feature/add-login
$ git push origin feature/add-login
# 対処法2: force push(チームで合意がある場合のみ)
$ git push --force-with-lease origin feature/add-login
そのため、Claude Codeに「mainに直接pushして」と指示するよりも、「featureブランチを作ってpushして」と指示する方が安全です。
図解: pushの仕組み
図解: Claude Codeの1タスクで起きるgit操作の全体像
本で例えると: 「蔵書目録を確認 → しおりを追加 → 読み進める → コピー機でスクラップ → 共有本棚に戻す」。この一連の流れが、Claude Codeの1タスクで起きていることです。
ここまで個別のgit操作を見てきました。では、Claude Codeに**「ログイン画面のバグ直して」**と指示したとき、実際に何が起きるのか全体像を見てみましょう。
各ステップで起きていることの整理
| ステップ | gitコマンド | 内部で起きること |
|---|---|---|
| ① fetch | git fetch origin |
refs/remotes/origin/を更新 |
| ② branch | git switch -c fix/login-bug |
新しいポインタ作成 + HEAD変更 |
| ③ 編集 | (gitは関係なし) | ワーキングツリーのファイルを変更 |
| ④ commit |
git add + git commit
|
blob→tree→commitオブジェクト作成 |
| ⑤ push | git push origin fix/login-bug |
ローカルのrefsとオブジェクトをリモートに送信 |
この流れを理解していれば、Claude Codeが何をやっているかが透明になります。
まとめ: 仕組みを知ると指示が変わる
この記事で見てきた内容を振り返ります。
- fetch — リモートの情報を取得するだけ(安全な確認)
- pull — fetch + merge(ローカルが変わる)
- branch — コミットを指すポインタを作るだけ(コストほぼゼロ)
- commit — プロジェクト全体のスナップショット(差分ではない)
- push — ローカルのrefsとオブジェクトをリモートに送信
これらを理解していると、Claude Codeへの指示が具体的で安全なものに変わります。
| ❌ 曖昧な指示 | ✅ 仕組みを理解した指示 |
|---|---|
| 「コード直して」 | 「featureブランチ切って修正して、pushしてPR出して」 |
| 「最新にして」 | 「fetchして差分確認してからpullして」 |
| 「mainにpushして」 | 「mainへの直pushは避けて、PRベースでマージして」 |
gitの仕組みは、Claude Codeを使わなくても役立つ基礎知識です。でもClaude Codeと一緒に働くとき、この知識があるとAIとの協業がもっとスムーズになります。
次回は「Claude Codeで起きがちなgit事故パターンとトラブルシュート」について解説する予定です。
関連記事
参考文献
Claude Codeの実践的な活用方法については、Zenn Book「Claude Code 実践ガイド」で詳しく解説しています。