Gitとバージョン管理

目次

概要

Gitは、ファイルの変更履歴を管理するための分散バージョン管理システムです。単なるバックアップではなく、「何を、なぜ、いつ、誰が変えたのか」を後から追えるようにするための開発基盤です。

要点

Gitの中心はcommit graphです。よいcommitは、レビュー、調査、rollback、リリース、共同開発を楽にします。statusdiffaddcommitbranchmergerebasepush は、単なるコマンドではなく履歴を設計するための操作です。

バージョン管理を学ぶ意味

バージョン管理がないと、開発では次の問題が起きます。

  • いつ壊れたのか分からない
  • 誰が何を変更したのか追えない
  • 複数人の作業を統合しにくい
  • リリース時点を再現できない
  • 元に戻すのが怖い
  • 調査のための比較ができない
  • 作業途中の状態を安全に保存できない

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側の変更を手元へ持ってくるには fetchpull が必要です。

Gitが扱う主なものです。

名前 意味
blob ファイル内容
tree ディレクトリ構造
commit treeへの参照、親commit、作者、メッセージなど
branch commitを指す可動ポインタ
tag 特定commitへ付ける名前
remote 別repositoryへの参照

普段の操作では内部オブジェクトを意識しなくても使えますが、branchが「履歴そのもの」ではなく「commitを指す名前」だと知ると、mergeやrebaseが理解しやすくなります。

Gitの全体像

Gitの操作は、次の4つの場所を行き来していると考えると分かりやすくなります。

flowchart LR W["working tree<br>編集中のファイル"] -->|git add| I["index<br>次のcommitの下書き"] I -->|git commit| L["local repository<br>手元の履歴"] L -->|git push| R["remote repository<br>GitHubなど"] R -->|git fetch| L L -->|git restore / checkout| W

Git初学者が混乱しやすいのは、addcommitpush がすべて「保存」に見えることです。しかし、それぞれの保存先は違います。

操作 移動するもの 移動先
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の基本形です。

gitGraph commit id: "A" commit id: "B" branch feature checkout feature commit id: "C" commit id: "D" checkout main commit id: "E" merge feature id: "M"

この図では、feature branchB から分岐し、CD を積んだあと、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 treeindex を分けることで、同じファイル内の一部だけを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で迷ったら、まず statusdiff です。

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 してから logdiff を見る方が落ち着いて判断できます。

git diff HEAD..origin/main

pushでは、手元branchとremote branchの対応が重要です。

git push -u origin feature/search

-u はupstreamを設定します。以後は git pushgit 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を作ります。

gitGraph commit id: "A" commit id: "B" branch feature checkout feature commit id: "C" commit id: "D" checkout main commit id: "E" merge feature id: "M"

rebaseのイメージです。feature のcommitを、main の先頭から作り直したような履歴にします。

gitGraph commit id: "A" commit id: "B" commit id: "E" branch feature checkout feature commit id: "C-prime" commit id: "D-prime"

rebase後の C-primeD-prime は、元の CD と似た変更を持ちますが、親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解消の流れです。

  1. 競合箇所を読む
  2. 両方の変更意図を確認する
  3. 正しい最終形に編集する
  4. conflict markerを消す
  5. テストやビルドを実行する
  6. git add する
  7. 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とつながって初めて開発基盤になります。典型的な流れは次の通りです。

flowchart LR A["feature branch"] --> B["push"] B --> C["Pull Request"] C --> D["CI<br>test / lint / build"] D --> E{"review OK?"} E -->|no| F["追加commit"] F --> C E -->|yes| G["merge"] G --> H["deploy workflow"] H --> I["production"]

この流れでGitが担う役割は、単にファイルを共有することではありません。

段階 Gitの役割 確認すること
branch 作業を隔離する branch名、作業範囲
commit 変更理由を記録する 粒度、message、差分
push remoteに共有する upstream、push先
PR レビュー単位を作る 説明、影響範囲、確認方法
merge 本流へ統合する CI、review、conflict
tag リリース点を固定する version、rollback点

deployと組み合わせる場合、重要なのは「どのbranchが本番に対応するか」を明確にすることです。

flowchart TD Develop["develop<br>日々の統合"] --> PR["PR"] PR --> Prod["prod<br>本番対応branch"] Prod --> Action["GitHub Actions"] Action --> Build["distを同期"] Build --> CDN["CloudFront invalidation"]

本番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は、他の人の作業に影響するため慎重に扱います。

Gitコマンドの影響範囲とリスク

この図では、履歴や共同作業への影響が大きく、誤解したときの危険が高い操作ほど右上に置いています。statusdiff は読む操作なので安全ですが、reset --hardforce push は履歴や作業内容を変えるため、実行前に現在branch、差分、remoteとの関係を確認します。

flowchart TD A["戻したいものは何か"] --> B{"まだcommitしていない変更か"} B -->|yes| C["git restore<br>working tree / indexを戻す"] B -->|no| D{"共有済みcommitか"} D -->|yes| E["git revert<br>打ち消すcommitを作る"] D -->|no| F{"履歴自体を動かしたいか"} F -->|yes| G["git reset<br>soft / mixed / hardを選ぶ"] F -->|no| H["新しいcommitで修正する"]

restoreresetrevert は「戻す」という日本語では同じに見えますが、履歴を残すか、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を積むだけでは履歴に残ります。次を行います。

  1. keyやtokenを失効・ローテーションする
  2. 公開範囲を確認する
  3. 必要なら履歴から削除する
  4. 再発防止として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

使い分け:

  • FFマージ: 小さなfix、地元のfeature branch
  • no-FFマージ: 重要な機能、リリース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し、statusdiff で確認し、branchとPRで作業を分け、共有済み履歴の書き換えには慎重になる。この基本ができると、レビュー、調査、リリース、rollbackが安定します。

実務では hook、CI、ブランチ保護ルール、標準的なコミットメッセージ形式を組み合わせることで、チーム全体でのGit運用品質が上がります。

参考文献

解説・補助