Git
読了目安 50 分
- ブランチが何であるかを説明できる
- コンフリクトを自分で解消できる
- reset --hard してしまった作業を reflog で復旧できる
Git は、あなたが毎日使う道具です。そして新人が最も恐れる道具でもあります。
「変な操作をしたら過去の作業が消えるのでは」という不安は、正しい直感です。
実際、git reset --hard は作業を消します。ただしほぼすべての操作は元に戻せます。
この章のゴールは、コマンドを暗記することではなく、
Git が何を保存しているのかを理解して、怖がらずに操作できるようになることです。
Git は何を解決したのか
Git がなかった時代、ファイルはこう管理されていました。
api_v1.zip
api_v2.zip
api_v2_修正版.zip
api_v2_修正版_最終.zip
api_v2_修正版_最終_これ使って.zip ← どれが最新?
Git が解決するのは3つです。
- いつ・誰が・なぜ変えたかが分かる(履歴)
- 複数人が同じファイルを同時に触れる(ブランチとマージ)
- いつでも過去に戻れる(コミット)
3つの場所
ここが Git 最大のつまずきポイントです。Git にはファイルの状態を置く場所が3つあります。
[作業ツリー] → [ステージ] → [コミット]
手元で編集中 git add で載せる git commit で確定
した状態 (選ぶ場所) (履歴に残る)
なぜ2段階なのかというと、コミットする内容を選べるからです。
5つのファイルを直したけれど、そのうち3つだけを「バグ修正」としてコミットしたい。 残り2つは別の作業なので後で別のコミットにしたい。こういう時にステージが役立ちます。
git status は、この3つの場所の差を表示するコマンドです。
README.md に開発環境の説明を追記しました。この変更をステージに載せて、「READMEに開発環境の手順を追加」というメッセージでコミットしてください。
コマンドを入力して Enter を押してください。
- 未達「READMEに開発環境の手順を追加」というコミットがある
- 未達未コミットの変更が残っていない
help で使えるコマンドが見られますGit で迷子になったら git status です。
今どのブランチにいて、何がステージされていて、何がされていないかを教えてくれます。
しかも次に何をすればいいかまで書いてくれます。 エラーメッセージと同じく、読めば答えが書いてあります。
コミットの正体
コミットは「差分」ではありません。その時点の全ファイルのスナップショットです。 そして親コミットへのポインタを持っています。
[最初のコミット] ← [READMEを追加] ← [検索機能を追加]
↑
HEAD
- 矢印は「親」を指しています。つまり履歴は後ろ向きに繋がった鎖です
- HEAD は「今どこを見ているか」を示す印です
この構造が分かると、あとの操作がすべて「ポインタをどう動かすか」の話になります。
コミットメッセージには WHY を書く
悪い: 修正
悪い: README.md を変更
良い: READMEに開発環境の手順を追加(新メンバーが環境構築で詰まったため)
何を変えたかは差分を見れば分かります。 分からないのは「なぜ変えたか」です。 3ヶ月後にこのコミットを見る人(たいてい自分)のために書いてください。
ブランチはポインタでしかない
「ブランチを切る」と聞くと、リポジトリ全体がコピーされる重い操作を想像しがちです。 実際はコミットを指す名札を1つ増やすだけです。だから一瞬で終わります。
main ──────→ [c1] ← [c2]
↑
add-search(同じコミットを指している)
この状態から add-search でコミットすると、動くのは add-search だけです。
main ──────→ [c1] ← [c2] ← [c3]
↑ ↑
main add-search
add-search という新しいブランチを作ってそちらに移り、search.md というファイルを作って「検索機能の設計メモを追加」というメッセージでコミットしてください。
コマンドを入力して Enter を押してください。
- 未達add-search ブランチにいる
- 未達ブランチ add-search の先頭が「検索機能の設計メモを追加」になっている
- 達成ブランチ main の先頭が「READMEに開発環境の手順を追加」になっている
help で使えるコマンドが見られます演習後にグラフを見てください。main は動いていないはずです。
ブランチはポインタ、というのはこういう意味です。
switch と checkout
歴史的な事情で、ブランチを切り替えるコマンドが2つあります。
| コマンド | 用途 |
|---|---|
git switch ブランチ名 | ブランチを切り替える(新しく、こちらが推奨) |
git switch -c 新ブランチ名 | 作って切り替える |
git checkout ブランチ名 | 同じことができる(古い。ファイルの復元など他の機能も持つ) |
checkout は機能を詰め込みすぎて分かりにくかったため、
ブランチ操作は switch、ファイル復元は restore に分割されました。
新しく書くなら switch を使ってください。
ただし既存のドキュメントや先輩の口からは checkout が出てくるので、両方知っておく必要があります。
マージ
ブランチで作った変更を、main に取り込む操作です。2つのパターンがあります。
fast-forward
main が動いていない場合、ポインタを進めるだけで済みます。
before: main → [c2], feature → [c4]
after: main → [c4](ただ進めるだけ。新しいコミットは作られない)
3-way マージ
両方が進んでいる場合、合流するコミット(マージコミット)が作られます。
┌── [c3] ──┐
[c1] ←──┤ ├── [c5] マージコミット(親が2つ)
└── [c4] ──┘
Git は共通の祖先(c1)を基準に、「どちらが変えたか」を見て自動で統合します。
mainだけが変えたファイル →mainの内容を使うfeatureだけが変えたファイル →featureの内容を使う- 両方が同じファイルの同じ場所を変えた → コンフリクト
コンフリクトは壊れていない
コンフリクトが起きると、新人は「壊した」と思って固まります。違います。
Git が「自動では決められないので、あなたが決めてください」と言っているだけです。
ファイルを開くと、こうなっています。
<<<<<<< HEAD
timeout: 60
=======
timeout: 10
retry: 3
>>>>>>> tune-timeout
| 部分 | 意味 |
|---|---|
<<<<<<< HEAD から ======= まで | 今いるブランチ(自分側)の内容 |
======= から >>>>>>> まで | 取り込もうとしているブランチの内容 |
やることは3つです。
- マーカーごと消して、正しい内容だけを残す
git add ファイル名で「解決した」と伝えるgit commitでマージを完了する
<<<<<<< や ======= を消し忘れてコミットすると、
コードにその文字列が残ったまま本番に出ます。構文エラーで気づければ幸運です。
解決したら、必ず git diff か grep -r '<<<<<<<' . で確認してください。
tune-timeout ブランチを main に取り込んでください。コンフリクトが起きるので、timeout は 30、retry は 3 になるように config.yml を直して、マージを完了させてください。
コマンドを入力して Enter を押してください。
- 未達config.yml の内容が指定どおりになっている
- 達成コンフリクトが解消されている
- 未達「タイムアウト設定をマージ」というコミットがある
help で使えるコマンドが見られますコンフリクトした相手のコミットを書いた人に聞いてください。
git log でその変更を入れた人と理由が分かります。
勝手にどちらかを消すと、相手の意図した修正が消えます。 これは技術の問題ではなくコミュニケーションの問題です。
rebase と、やってはいけないこと
rebase は、コミットの土台を差し替えて履歴を書き換える操作です。
before: [c1] ← [c2] ← [c3](main)
└ [c4] ← [c5](feature)
git switch feature && git rebase main
after: [c1] ← [c2] ← [c3](main) ← [c4'] ← [c5'](feature)
c4 c5 は新しい別のコミット(c4' c5')として作り直されます。
履歴が一直線になるので読みやすくなります。
これが Git の禁止事項の中で最も重要です。
rebase はコミットを作り直すので、ID が変わります。 すでに push して他の人が取り込んでいるブランチでこれをやると、 相手の手元の履歴と一致しなくなり、全員が混乱します。
自分しか触っていないブランチなら安全、と覚えてください。 チームのルールで「main への rebase 禁止」などが決まっていることも多いので、 最初に確認してください。
この教科書のターミナルでは rebase は動きません(実機で試してください)。
やらかしからの復旧
ここが一番実用的な節です。Git のほとんどの事故は復旧できます。
| やってしまったこと | 復旧方法 |
|---|---|
| ステージに載せたのを取り消したい | git restore --staged ファイル名 |
| 作業ツリーの変更を捨てたい | git restore ファイル名 |
| 直前のコミットメッセージを直したい | git commit --amend -m "新しいメッセージ" |
| コミットを取り消したい(履歴を残す) | git revert コミットID |
| コミットを取り消したい(履歴も消す) | git reset --hard HEAD~1 |
| reset --hard で消してしまった | git reflog から戻す |
| 作業中だけど別ブランチに移りたい | git stash → 移動 → git stash pop |
revert と reset の違い
| 履歴 | 使う場面 | |
|---|---|---|
revert | 打ち消すコミットを新しく作る | すでに push した・共有済みのもの |
reset | コミットそのものを無かったことにする | 手元だけの、まだ push していないもの |
共有済みのものは revert です。reset すると他の人の履歴と食い違います。
reflog は命綱
git reflog は、HEAD が動いた履歴を全部持っています。
$ git reflog
a1b2c3d HEAD@{0}: reset: moving to HEAD~1
9f8e7d6 HEAD@{1}: commit: 大事な実装を追加 ← 消したはずのコミット
c3d4e5f HEAD@{2}: commit: READMEに手順を追加
reset --hard で消えたように見えたコミットも、まだ存在しています
(Git はしばらく消しません)。ID が分かれば戻れます。
まず git reset --hard HEAD~1 を実行して、直前のコミット(大事な実装を追加)を消してしまってください。そのあと reflog を使って元に戻してください。
コマンドを入力して Enter を押してください。
- 未達reset --hard で消してしまった
- 未達reflog で履歴を確認した
- 達成payment.md が存在する
- 達成ブランチ main の先頭が「大事な実装を追加」になっている
help で使えるコマンドが見られます新人が最もパニックになるのが reset --hard の事故です。
覚えておくのはこの1行だけでいいです。
git reflog を見れば、たいてい戻せる。
PR ベースの流れ
実務では、main に直接コミットしません。この流れになります。
1. main を最新にする git switch main && git pull
2. 作業ブランチを作る git switch -c fix-search-timeout
3. 実装してコミット git add . && git commit -m "..."
4. リモートに push git push -u origin fix-search-timeout
5. GitHub で PR を作る
6. レビューを受けて直す (コミットを追加して push)
7. 承認されたらマージ
8. ブランチを消す git switch main && git pull && git branch -d fix-search-timeout
ブランチ名
チームの慣習に従いますが、よくある形はこれです。
feature/商品検索の説明文対応
fix/決済のタイムアウト
PROJ-123-add-search ← チケット番号を入れる方式
何をするブランチか分かる名前にしてください。test tmp mybranch は避けます。
ブランチを切るのを忘れて main でコミットしてしまった場合、慌てなくて大丈夫です。
git branch fix-search # 今の位置に新しいブランチを作る
git reset --hard origin/main # main を push 済みの位置に戻す
git switch fix-search # 作ったブランチに移動(作業はここに残っている)コミットは消えず、ブランチの名札だけが付け替わります。
実務の落とし穴まとめ
- 秘密情報をコミットした — 履歴に残るので、削除しても消えません。 気づいた時点ですぐ報告してください(鍵の無効化が必要です)
git add .で余計なファイルを入れる —git statusで確認してから add する- コンフリクトのマーカーを消し忘れる — 解決後に
git diffで確認 - push 済みブランチを rebase する — 全員の履歴が食い違う
- コミットメッセージが「修正」 — 3ヶ月後の自分が困る
- 巨大な1コミット — 意味のある単位で分ける。レビューしやすさに直結する
API キーやパスワードをコミットして push してしまった場合、 ファイルを消すコミットを追加しても解決しません。履歴から誰でも取り出せます。
やることは2つです。
- その鍵を無効化する(最優先) — これが本当の対処です
- チームに報告する
履歴から完全に消す方法もありますが、共有リポジトリでは影響が大きいので、 必ず先輩に相談してください。隠さないことが一番大事です。
まとめ
- Git には作業ツリー・ステージ・コミットの3層がある。
git statusはその差を見せる - コミットはスナップショット + 親へのポインタ。履歴は後ろ向きの鎖
- ブランチはポインタでしかない。だから作るのが一瞬で、怖くない
- コンフリクトは壊れていない。マーカーを消して add して commit すれば終わる
- 共有済みは
revert、手元だけならreset reset --hardしてもgit reflogで戻せる- push 済みブランチを rebase しない
- 秘密情報をコミットしたら、鍵を無効化して報告
章末問題
git switch -c feature でブランチを作ってコミットしました。main はどうなっていますか。
すでに push してチームが取り込んでいるコミットに、バグがあると分かりました。取り消す方法は?
git reset --hard HEAD~1 を実行してしまい、3時間かけた作業が消えました。最初にやることは?
次の章からは第2部です。チームでこの Git をどう使って開発を回すのか、 という仕事の流れの話に移ります。