Gitとバージョン管理
目次
- 概要
- バージョン管理を学ぶ意味
- Gitの基本モデル
- Gitの全体像
- working tree・index・repository
- commitを理解する
- statusとdiff
- addとstaging
- branch
- remoteとtracking branch
- fetch・pull・push
- mergeとrebase
- conflictの考え方
- commitの粒度とメッセージ
- pull request
- PRからdeployまでの流れ
- tagとrelease
- restore・reset・revert
- stashとworktree
- 履歴を調べる
- bisectで原因を探す
- gitignoreと秘密情報
- チーム運用の型
- 安全に使うための注意
- Git hook と自動化
- リモートリポジトリの操作応用
- PR/レビュープロセスの実装パターン
- 分散開発での競合・マージ戦略
- 本番トラブルシューティング
- まとめ
- 参考文献
概要
Gitは、ファイルの変更履歴を管理するための分散バージョン管理システムです。単なるバックアップではなく、「何を、なぜ、いつ、誰が変えたのか」を後から追えるようにするための開発基盤です。
Gitの中心はcommit graphです。よいcommitは、レビュー、調査、rollback、リリース、共同開発を楽にします。status、diff、add、commit、branch、merge、rebase、push は、単なるコマンドではなく履歴を設計するための操作です。
バージョン管理を学ぶ意味
バージョン管理がないと、開発では次の問題が起きます。
- いつ壊れたのか分からない
- 誰が何を変更したのか追えない
- 複数人の作業を統合しにくい
- リリース時点を再現できない
- 元に戻すのが怖い
- 調査のための比較ができない
- 作業途中の状態を安全に保存できない
Gitは、変更をcommitとして記録し、それらをgraphとしてつなぐことで履歴を表します。branchは履歴上の作業線であり、remoteは別のrepositoryです。この3つを理解すると、Gitの多くの操作が見通しやすくなります。
commit -- commit -- commit
\
commit -- commit
Gitは「保存ボタン」ではありません。変更の意図を小さく記録し、共有し、必要なときに戻れるようにするための仕組みです。
Gitの基本モデル
Gitの特徴は、分散バージョン管理であることです。GitHubにあるrepositoryだけが本体ではありません。手元の .git ディレクトリにも履歴があります。
local repository <----> remote repository
手元でcommitしただけでは、GitHubには反映されません。remoteに送るには push が必要です。逆に、GitHub側の変更を手元へ持ってくるには fetch や pull が必要です。
Gitが扱う主なものです。
| 名前 | 意味 |
|---|---|
| blob | ファイル内容 |
| tree | ディレクトリ構造 |
| commit | treeへの参照、親commit、作者、メッセージなど |
| branch | commitを指す可動ポインタ |
| tag | 特定commitへ付ける名前 |
| remote | 別repositoryへの参照 |
普段の操作では内部オブジェクトを意識しなくても使えますが、branchが「履歴そのもの」ではなく「commitを指す名前」だと知ると、mergeやrebaseが理解しやすくなります。
Gitの全体像
Gitの操作は、次の4つの場所を行き来していると考えると分かりやすくなります。
Git初学者が混乱しやすいのは、add、commit、push がすべて「保存」に見えることです。しかし、それぞれの保存先は違います。
| 操作 | 移動するもの | 移動先 |
|---|---|---|
git add |
working treeの変更 | index |
git commit |
indexの内容 | local repository |
git push |
local commit | remote repository |
git fetch |
remoteの参照 | local repository |
git pull |
remoteの参照と統合結果 | local branch |
commit graphの基本形です。
この図では、feature branchは B から分岐し、C と D を積んだあと、main にmergeされています。branchはcommitを指す名前なので、commitが増えるたびに指す先が動きます。
working tree・index・repository
Gitには大きく3つの領域があります。
| 領域 | 役割 |
|---|---|
| working tree | 実際に編集しているファイル |
| index(staging area) | 次のcommitに入れる内容 |
| repository | commitと履歴 |
基本の流れです。
edit -> git add -> git commit -> git push
git add は保存ではありません。「次のcommitに入れる変更を選ぶ」操作です。git commit は、indexの内容を履歴として記録します。
git status
git diff
git add README.md
git diff --staged
git commit -m "READMEを更新"
working tree と index を分けることで、同じファイル内の一部だけをcommitできます。
git add -p
この感覚が分かると、Gitはかなり扱いやすくなります。
commitを理解する
commitは、ある時点のsnapshotと、その説明です。差分だけを保存しているように見えますが、Gitの考え方としては「その時点のtreeを指す履歴ノード」と捉える方が自然です。
commitには次の情報が含まれます。
- treeへの参照
- 親commit
- 作者
- committer
- 日時
- commit message
- commit hash
commit hashは内容から計算される識別子です。履歴を書き換えるとhashも変わります。
よいcommitは、後から読んだ人に「なぜこの変更が必要だったのか」を伝えます。
弱い: fix
弱い: update
良い: 認証エラー時のレスポンス形式を統一
良い: deploy失敗時にCloudFront invalidationを実行しない
statusとdiff
Gitで迷ったら、まず status と diff です。
git status
git status --short
git diff
git diff --staged
それぞれの意味です。
| コマンド | 見るもの |
|---|---|
git status |
working treeとindexの状態 |
git diff |
working treeとindexの差分 |
git diff --staged |
indexとHEADの差分 |
git log --oneline |
commit履歴 |
commit前の基本確認です。
git status
git diff
git diff --staged
この3つを見る習慣があるだけで、入れるつもりのない変更をcommitする事故がかなり減ります。
addとstaging
git add は、ファイル内容をindexに追加します。Git公式ドキュメントでも、indexは次のcommitの内容を準備する場所として説明されています。
git add README.md
git add src/
git add -A
代表的な使い方です。
| コマンド | 意味 |
|---|---|
git add file |
指定ファイルをstage |
git add -A |
追加・変更・削除をまとめてstage |
git add -u |
追跡済みファイルの変更・削除をstage |
git add -p |
hunk単位で対話的にstage |
git add -N file |
intent-to-add。未追跡ファイルのdiffを見たいとき |
git add -p は、commitをきれいに分けるために非常に便利です。
git add -p
git diff --staged
git commit -m "フォーム入力の検証を追加"
一度 add したあとに同じファイルをさらに編集した場合、その追加分はindexに入りません。もう一度 git add する必要があります。
branch
branchは、履歴上の作業線です。実体としてはcommitを指す可動ポインタです。
git switch -c feature/search
既存branchへ移動します。
git switch main
branchは軽量なので、作業単位ごとに切るのが一般的です。
git switch -c fix/header-link
git switch -c docs/update-readme
branch名は、作業の意図が分かるようにします。
| 例 | 用途 |
|---|---|
feature/search |
新機能 |
fix/login-error |
バグ修正 |
docs/deploy-guide |
ドキュメント |
refactor/sidebar-state |
リファクタリング |
branchを切る目的は、作業を隔離し、レビューや検証をしやすくすることです。
remoteとtracking branch
Gitは分散型なので、手元のrepositoryとGitHubなどのremote repositoryは別物です。
git remote -v
origin/main のような名前はremote-tracking branchです。これは「最後にfetchした時点で、remote側のbranchがどこを指していたか」を表すローカルの参照です。
git fetch origin
git branch -vv
git branch -vv は、現在のbranchがどのupstream branchを追跡しているかを見るのに便利です。
* develop 25fae1c [origin/develop] gh削除
tracking branchを理解すると、「pushしたのに反映されない」「pullしたらmerge commitができた」「手元とGitHubの差分が分からない」といった混乱が減ります。
fetch・pull・push
remoteと同期する基本操作です。
| 操作 | 役割 |
|---|---|
git fetch |
remoteの情報を取得する。working treeは変えない |
git pull |
fetchしたうえで、現在branchに統合する |
git push |
手元のcommitをremoteへ送る |
安全に確認したいときは、まずfetchします。
git fetch origin
git log --oneline --graph --decorate --all -20
pull は便利ですが、内部で統合まで行います。何が起きるか見たい場合は、fetch してから log や diff を見る方が落ち着いて判断できます。
git diff HEAD..origin/main
pushでは、手元branchとremote branchの対応が重要です。
git push -u origin feature/search
-u はupstreamを設定します。以後は git push や git pull が簡潔になります。
mergeとrebase
merge は、2つの履歴を統合するcommitを作ります。
git switch main
git merge feature/search
rebase は、自分の変更を別の履歴の先頭へ付け替えます。
git switch feature/search
git rebase main
使い分けです。
| 方法 | 向いている場面 |
|---|---|
| merge | 統合の事実を履歴に残したい |
| rebase | 自分の作業branchを読みやすく並べ直したい |
| squash merge | PR単位で1 commitにまとめたい |
rebaseは履歴を書き換えます。共有済みbranchを不用意にrebaseすると、他の人の履歴と食い違います。基本方針として、自分だけの作業branchで履歴を整える用途に向いています。
mergeのイメージです。分岐した履歴を残したまま、統合commitを作ります。
rebaseのイメージです。feature のcommitを、main の先頭から作り直したような履歴にします。
rebase後の C-prime と D-prime は、元の C と D と似た変更を持ちますが、親commitが変わるため別commitです。つまりhashも変わります。このため、共有済みbranchでrebaseすると、他の人が持っている履歴とずれる可能性があります。
git rebase -i main
interactive rebaseでは、commitの並べ替え、まとめ、メッセージ修正ができます。ただし、すでに共有したcommitに対して使う場合は、チームで合意してから行います。
conflictの考え方
conflictは、Gitが自動で安全に統合できない変更です。同じ箇所を複数のbranchが変更したときなどに起きます。
<<<<<<< HEAD
current branch
=======
incoming branch
>>>>>>> feature/search
見るべきことは、「どちらを残すか」だけではありません。両方の意図を満たす第三の形に直すことも多いです。
conflict解消の流れです。
- 競合箇所を読む
- 両方の変更意図を確認する
- 正しい最終形に編集する
- conflict markerを消す
- テストやビルドを実行する
git addする- mergeまたはrebaseを続ける
git status
git add conflicted-file.md
git rebase --continue
conflictは失敗ではありません。Gitが勝手に危険な統合をしなかった、という合図です。
commitの粒度とメッセージ
よいcommitは、後から読んだ人が意図を追える単位です。
目安です。
- 1 commitに1つの理由
- 動く状態でcommitする
- unrelatedな変更を混ぜない
- messageは「何を」だけでなく「なぜ」も意識する
- 大きな変更は段階に分ける
commit messageの例です。
サイドバー開閉状態をページ遷移後も保持
本文を付ける場合です。
サイドバー開閉状態をページ遷移後も保持
章リンクをクリックしたときにサイドバーが先頭へ戻り、
長い目次で現在位置を見失いやすかったため。
sessionStorageにscrollTopを保存して復元する。
小さすぎるcommitは履歴を読みにくくし、大きすぎるcommitはレビューとrollbackを難しくします。ちょうどよい粒度は、変更理由が1文で説明できる単位です。
pull request
Pull Requestは、変更をレビューし、議論し、統合するための単位です。単なるmergeボタンではなく、設計判断の記録でもあります。
よいPRに含める情報です。
- 何を変えたか
- なぜ変えたか
- 影響範囲
- 確認方法
- スクリーンショットやログ
- 残課題
PRを小さく保つと、レビューの質が上がります。大きな変更が必要な場合でも、機械的変更、構造変更、挙動変更、デザイン調整を分けると見やすくなります。
PRの説明例です。
## 変更内容
- サイドバーの章グループを開閉可能にした
- 現在ページを含むセクションだけ初期表示で開くようにした
## 確認
- npm run build
- ローカルでサイドバー開閉とページ遷移を確認
レビューでは、好みだけでなく、仕様、保守性、テスト、ユーザー影響を軸に見ると建設的になります。
PRからdeployまでの流れ
Gitは単体で使うだけでなく、CI/CDとつながって初めて開発基盤になります。典型的な流れは次の通りです。
この流れでGitが担う役割は、単にファイルを共有することではありません。
| 段階 | Gitの役割 | 確認すること |
|---|---|---|
| branch | 作業を隔離する | branch名、作業範囲 |
| commit | 変更理由を記録する | 粒度、message、差分 |
| push | remoteに共有する | upstream、push先 |
| PR | レビュー単位を作る | 説明、影響範囲、確認方法 |
| merge | 本流へ統合する | CI、review、conflict |
| tag | リリース点を固定する | version、rollback点 |
deployと組み合わせる場合、重要なのは「どのbranchが本番に対応するか」を明確にすることです。
本番deployをbranch mergeに連動させるなら、prod への直接pushを禁止し、PR経由にするのが安全です。GitHubのbranch protection、required status checks、review requirementを組み合わせると、誤deployを減らせます。
rollbackの考え方も決めておきます。
| 状況 | 対応 |
|---|---|
| 直近mergeが問題 | revert PRを作る |
| 特定releaseに戻したい | tagやrelease commitを基準に戻す |
| 設定だけが問題 | code rollbackではなく設定を戻す |
| secret漏えい | rollbackではなくsecret rotation |
Gitの履歴とdeployの履歴が対応していると、「いま本番に出ているもの」を説明しやすくなります。
tagとrelease
tagは、特定のcommitに名前を付ける仕組みです。リリース地点を固定するために使います。
git tag v1.0.0
git push origin v1.0.0
annotated tagは、タグ作成者、日時、メッセージを持つtag objectを作ります。リリースに使うtagではannotated tagを選ぶことが多いです。
git tag -a v1.0.0 -m "Release v1.0.0"
git push origin v1.0.0
セマンティックバージョニングでは、MAJOR.MINOR.PATCH の形をよく使います。
1.2.3
ただし、バージョン番号はプロジェクトの契約です。ライブラリ、アプリ、ドキュメントサイトでは意味が少し違うため、チームのリリース方針に合わせます。
restore・reset・revert
変更を戻すコマンドは名前が似ていますが、意味が違います。
| コマンド | 主な用途 | 注意 |
|---|---|---|
git restore |
working treeやindexの変更を戻す | 未保存変更を消すことがある |
git reset |
branchやindexの位置を動かす | --hard はworking treeも消す |
git revert |
既存commitを打ち消す新しいcommitを作る | 共有済み履歴で安全に使いやすい |
作業中の変更を戻す例です。
git restore README.md
git restore --staged README.md
直近commitを取り消してindexに戻す例です。
git reset --soft HEAD~1
共有済みcommitを打ち消す例です。
git revert abc1234
共有済み履歴では、まず revert を検討します。reset --hard やforce pushは、他の人の作業に影響するため慎重に扱います。
この図では、履歴や共同作業への影響が大きく、誤解したときの危険が高い操作ほど右上に置いています。status や diff は読む操作なので安全ですが、reset --hard や force push は履歴や作業内容を変えるため、実行前に現在branch、差分、remoteとの関係を確認します。
restore、reset、revert は「戻す」という日本語では同じに見えますが、履歴を残すか、indexを動かすか、working treeを消すかが違います。迷ったら、共有済み履歴では revert、未commitの作業では restore、自分だけの履歴整理では reset と考えると安全です。
stashとworktree
途中の変更を一時退避したいときは stash を使えます。
git stash push -m "wip before hotfix"
git stash list
git stash pop
stashは短期退避には便利ですが、長期保存場所ではありません。何を入れたか忘れやすいため、長く残す作業はbranchにcommitした方が追いやすくなります。
複数branchを同時に触りたいときは git worktree が便利です。
git worktree add ../site-hotfix hotfix/header-link
1つのrepositoryから別ディレクトリにworking treeを増やせます。ビルド中に別branchを見たい場合や、緊急修正を切り出したい場合に使えます。
worktree一覧です。
git worktree list
削除です。
git worktree remove ../site-hotfix
履歴を調べる
履歴を読む力は、Gitの大事な実務スキルです。
git log --oneline --graph --decorate --all
git show HEAD
git show abc1234
ファイルの変更履歴を追う例です。
git log -- README.md
git log -p -- README.md
誰が最後に変更したかを見る例です。
git blame README.md
blame は責任追及の道具ではなく、文脈を探す道具です。変更理由を知りたいときは、該当commitとPRを合わせて読むと理解しやすくなります。
差分範囲を見る例です。
git diff main..develop
git log --oneline main..develop
このようなrange指定は、PR作成前やdeploy前の確認に役立ちます。
bisectで原因を探す
git bisect は、二分探索でバグを入れたcommitを探す機能です。
git bisect start
git bisect bad
git bisect good v1.0.0
Gitが途中のcommitに移動するので、テストして良いか悪いかを伝えます。
npm test
git bisect good
git bisect bad
終わったら戻します。
git bisect reset
自動化もできます。
git bisect run npm test
bisectは、履歴が「各commitである程度動く」状態だと強力です。壊れた中間commitが多いと、原因特定が難しくなります。
gitignoreと秘密情報
.gitignore は、Gitがまだ追跡していないファイルを無視するための設定です。
node_modules/
dist/
.env
*.log
すでにcommit済みのファイルは、.gitignore に追加しても自動では消えません。
git rm --cached .env
秘密情報を誤ってcommitした場合、削除commitを積むだけでは履歴に残ります。次を行います。
- keyやtokenを失効・ローテーションする
- 公開範囲を確認する
- 必要なら履歴から削除する
- 再発防止としてsecret scanningやpre-commit hookを検討する
一度公開されたsecretは、第三者に読まれた前提で扱う方が安全です。
チーム運用の型
Gitの運用は、プロジェクト規模やリリース方法によって変わります。
代表的な型です。
| 運用 | 特徴 |
|---|---|
| trunk-based development | mainに小さく頻繁に統合する |
| GitHub Flow | feature branchからPRを作りmainに統合する |
| Git Flow | develop、release、hotfixなどbranchを分ける |
| release branch運用 | リリース単位で安定branchを持つ |
小さなチームやWebサービスでは、短命branchとPRを使う運用が扱いやすいことが多いです。長期branchが増えるほど、統合コストとconflictが増えます。
運用を決めるときの観点です。
- どのbranchが本番にdeployされるか
- PR reviewは必須か
- squash mergeかmerge commitか
- release tagを付けるか
- hotfixをどこから切るか
- CIがどのタイミングで走るか
- force pushを許す範囲はどこか
Gitの運用は、履歴の美しさだけでなく、リリース速度、障害対応、レビュー負荷に影響します。
安全に使うための注意
Gitは履歴を扱う強力な道具です。特に履歴を書き換える操作には注意します。
git statusをよく見る- 変更前に
git diffを確認する reset --hardは慎重に使う- 共有済みbranchのforce pushは避ける
- 大きなバイナリを不用意に入れない
- secretをcommitしない
.gitignoreを整える- rebase前に対象branchを確認する
- conflict解消後は必ずテストする
危険な操作の前には、現在地を確認します。
git status
git branch --show-current
git log --oneline -5
履歴を書き換える前に一時branchを作るのも有効です。
git branch backup/before-rebase
Gitは多くの場合、reflogから復旧できます。
git reflog
ただし、reflogは万能のバックアップではありません。push済みか、他の人が取得済みか、secretを含むかによって対応は変わります。
Git hook と自動化
実務では、手動の確認では漏れが発生するため、自動チェックが重要です。
pre-commitとpre-push hook
# .git/hooks/pre-commit (実行可能にする)
#!/bin/bash
# secretスキャン
if git diff --cached | grep -E 'password|api_key|secret' ; then
echo "Secrets detected in staged changes!"
exit 1
fi
# linting
npm run lint --staged
GitHubの保護ルール
リポジトリ設定で以下を推奨します:
- Require branches to be up to date before merging (main branchへのマージ前にrebase必須)
- Require status checks to pass (CIが成功する必要がある)
- Require code reviews before merging (最低1人のレビュー必須)
- Restrict who can push to matching branches (mainへの直接pushを禁止)
- Allow auto-merge (スクワッシュマージを許可)
Git commit graph の最適化
大規模リポジトリでは、commit graphが肥大化し操作が遅くなります。
# packファイル最適化
git gc --aggressive
# commit graph生成(Git 2.40+)
git commit-graph write --reachable
リモートリポジトリの操作応用
upstream と fork
フォークされたリポジトリで upstream を管理します:
git remote add upstream https://github.com/original/repo.git
git fetch upstream
git rebase upstream/main
shallow clone と submodule
大規模リポジトリの場合:
# 最新履歴だけをクローン(容量削減)
git clone --depth 1 https://github.com/org/repo.git
# 後から完全な履歴を取得
git fetch --unshallow
Submoduleで外部依存を管理:
git submodule add https://github.com/dep/module.git libs/module
git submodule update --init --recursive
PR/レビュープロセスの実装パターン
コミットメッセージの標準化
Conventional Commits を採用:
<type>(<scope>): <subject>
<body>
<footer>
---
type: feat, fix, docs, style, refactor, perf, test, chore
scope: affected module (optional)
subject: imperative, present tense, no period
例:
feat(auth): add JWT refresh token rotation
- Implement 30-minute refresh token expiry
- Add automatic token rotation on API calls
- Update token validation middleware
Closes #1234
Breaking-change: old token format no longer supported
PR reviewチェックリスト
実装者が準備:
- [ ] 最新の main から rebase済み
- [ ] squash merge対象なら1つのcommitに整形
- [ ] ローカルでテスト・ビルド完了
- [ ] ドキュメント・コメント更新済み
レビュアーが確認:
- [ ] 変更内容が問題説明に沿っているか
- [ ] edge caseの処理がされているか
- [ ] 既存テストが通るか
- [ ] 新しい依存が安全か
- [ ] パフォーマンス影響がないか
分散開発での競合・マージ戦略
fast-forward マージと merge commit
# fast-forward を許可(履歴が一直線)
git merge --ff feature-branch
# 統合commitを必ず作成
git merge --no-ff feature-branch
使い分け:
cherry-pick での選別的なマージ
特定のcommitのみをマージ:
git cherry-pick <commit-hash>
# 連続したcommitの場合
git cherry-pick <start>..<end>
リバート と修正
# 既にマージ済みのcommitを元に戻す
git revert -m 1 <merge-commit>
# 反対: 修正内容を revert したい場合
git revert <revert-commit>
本番トラブルシューティング
リリース後の緊急対応
# hotfixブランチを main から切る
git checkout main
git pull origin main
git checkout -b hotfix/critical-bug-1234
# 修正・commit
git add .
git commit -m "fix: restore user session handling"
# リリース tag を打つ
git tag v1.2.3-hotfix.1
git push origin hotfix/critical-bug-1234
git push origin v1.2.3-hotfix.1
誤ったマージの取り消し
# マージ前に戻す(マージcommit削除)
git revert -m 1 <merge-commit>
# または push済みなら新しいcommitで修正
git revert <bad-commit>
git push origin main
まとめ
Gitは、変更履歴を安全に共有するための基盤です。重要なのはコマンドを暗記することではなく、working tree、index、commit、branch、remoteの関係を理解することです。
よいGit運用は、よいcommitから始まります。意図が分かる単位でcommitし、status と diff で確認し、branchとPRで作業を分け、共有済み履歴の書き換えには慎重になる。この基本ができると、レビュー、調査、リリース、rollbackが安定します。
実務では hook、CI、ブランチ保護ルール、標準的なコミットメッセージ形式を組み合わせることで、チーム全体でのGit運用品質が上がります。
参考文献
解説・補助
- Pro Git Book
- Git Documentation: git-add
- Git Documentation: git-branch
- Git Documentation: git-commit
- Git Documentation: git-fetch
- Git Documentation: git-pull
- Git Documentation: git-push
- Git Documentation: git-rebase
- Git Documentation: git-reset
- Git Documentation: git-restore
- Git Documentation: git-revert
- Git Documentation: git-stash
- Git Documentation: git-worktree
- Git Documentation: gitignore
- Git Reference
- GitHub Docs: About pull requests
- GitHub Docs: Collaborating with pull requests
- GitHub Docs: Ignoring files