はじめに
Gitは現代の開発現場では必須のツールですが、基本的なコマンドだけを使っている方も多いのではないでしょうか。この記事では、現場で即戦力となるGitの実践テクニックを15個厳選してお届けします。
これらのテクニックを習得することで、開発効率が大幅に向上し、チーム開発でのトラブルも減らすことができます。
Gitの内部構造を理解する
Gitオブジェクトモデル
Gitを深く理解するには、内部構造を知ることが重要です。Gitは主に4種類のオブジェクトで構成されています。
┌─────────────────────────────────────────────────────────────┐
│ Git オブジェクト │
│ │
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ │
│ │ Blob │ │ Tree │ │ Commit │ │
│ │ (ファイル)│───▶│(ディレクトリ)│◀────│ │ │
│ └──────────┘ └──────────┘ └──────────┘ │
│ │ │ │
│ ▼ ▼ │
│ ┌──────────┐ ┌──────────┐ │
│ │ Tree │ │ Commit │ │
│ │ (sub) │ │ (parent) │ │
│ └──────────┘ └──────────┘ │
└─────────────────────────────────────────────────────────────┘
| オブジェクト | 説明 |
|---|---|
| Blob | ファイルの内容を保存 |
| Tree | ディレクトリ構造を保存(Blobや他のTreeへの参照) |
| Commit | ある時点のスナップショット(Treeへの参照と親コミット) |
| Tag | 特定のコミットへの名前付き参照 |
# オブジェクトの種類を確認
git cat-file -t abc1234
# オブジェクトの内容を表示
git cat-file -p abc1234
# コミットオブジェクトの例
git cat-file -p HEAD
# tree abc123...
# parent def456...
# author Name <email> 1234567890 +0900
# committer Name <email> 1234567890 +0900
#
# コミットメッセージ
ブランチとHEADの仕組み
ブランチは単なる「コミットへのポインタ」です。
# ブランチが指すコミットを確認
cat .git/refs/heads/main
# abc123def456...
# HEADが指すブランチを確認
cat .git/HEAD
# ref: refs/heads/main
┌─────────────────────────────────────────────────────────────┐
│ │
│ HEAD ──▶ main ──▶ [Commit C] ──▶ [Commit B] ──▶ [Commit A]│
│ │ │
│ feature ───────────────┘ │
│ │
└─────────────────────────────────────────────────────────────┘
ステージングエリア(インデックス)
Gitには3つの領域があります。
┌─────────────────────────────────────────────────────────────┐
│ │
│ [Working Directory] ──add──▶ [Staging Area] ──commit──▶ [Repository]│
│ 作業ディレクトリ ステージング リポジトリ │
│ │
│ ◀──────────────checkout────────────── │
│ │
└─────────────────────────────────────────────────────────────┘
# ステージングエリアの内容を確認
git ls-files --stage
# ステージングと作業ディレクトリの差分
git diff
# ステージングとリポジトリの差分
git diff --staged
基本操作を効率化するテクニック
git add -p で変更を部分的にステージング
ファイル内の変更を部分的にステージングしたい場合、git add -pが非常に便利です。
git add -p filename.js
このコマンドを実行すると、変更箇所ごとに対話的にステージングするかどうかを選択できます。
Stage this hunk [y,n,q,a,d,s,e,?]?
主なオプション:
-
y- この変更をステージング -
n- この変更をスキップ -
s- 変更をより細かく分割 -
e- 手動で編集
これにより、1つのファイルに複数の論理的な変更がある場合でも、コミットを適切に分割できます。
git stash で作業を一時保存
急な割り込み作業が発生した時、現在の変更を一時的に退避させるのに使います。
# 変更を退避
git stash
# メッセージ付きで退避
git stash save "WIP: ログイン機能の実装中"
# 退避した変更の一覧
git stash list
# 最新の退避を復元(stashから削除)
git stash pop
# 最新の退避を復元(stashに残す)
git stash apply
# 特定のstashを復元
git stash apply stash@{2}
git commit --amend で直前のコミットを修正
コミットメッセージを間違えた場合や、ファイルを追加し忘れた場合に使用します。
# コミットメッセージだけを修正
git commit --amend -m "新しいコミットメッセージ"
# ファイルを追加してコミットを修正
git add forgotten_file.js
git commit --amend --no-edit
注意: プッシュ済みのコミットに対して使用する場合は、git push --forceが必要になります。チーム開発では慎重に使用してください。
ブランチ操作の上級テクニック
git rebase -i で複数コミットを整理
プルリクエスト前にコミット履歴を整理したい場合、対話的リベースが強力です。
# 直近5コミットを対話的に編集
git rebase -i HEAD~5
エディタが開き、以下のような画面が表示されます:
pick abc1234 最初のコミット
pick def5678 2番目のコミット
pick ghi9012 3番目のコミット
主な操作:
-
pick- コミットをそのまま使用 -
reword- コミットメッセージを編集 -
squash- 前のコミットと統合(メッセージも統合) -
fixup- 前のコミットと統合(メッセージは破棄) -
drop- コミットを削除
pick abc1234 機能Aの実装
squash def5678 機能Aのバグ修正
squash ghi9012 機能Aのテスト追加
これにより、3つのコミットが1つにまとまります。
git cherry-pick で特定のコミットだけを適用
別ブランチの特定のコミットだけを現在のブランチに適用したい場合に使用します。
# 特定のコミットを適用
git cherry-pick abc1234
# 複数のコミットを適用
git cherry-pick abc1234 def5678
# コミットせずに変更だけを適用
git cherry-pick --no-commit abc1234
git worktree で複数ブランチを同時に作業
1つのリポジトリで複数のブランチを同時にチェックアウトできます。
# 新しいワークツリーを作成
git worktree add ../project-hotfix hotfix-branch
# ワークツリーの一覧
git worktree list
# ワークツリーを削除
git worktree remove ../project-hotfix
これにより、stashを使わずに別のブランチで作業を開始できます。
マージ戦略を理解する
マージの種類
Gitには主に3つのマージ方法があります。
┌─────────────────────────────────────────────────────────────┐
│ Fast-Forward マージ │
│ │
│ Before: │
│ main ──▶ A ──▶ B │
│ └──▶ C ──▶ D ◀── feature │
│ │
│ After (git merge feature): │
│ main ──▶ A ──▶ B ──▶ C ──▶ D ◀── main, feature │
│ │
└─────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────┐
│ 3-way マージ │
│ │
│ Before: │
│ ┌──▶ C ──▶ D ◀── feature │
│ main ──▶ A ──▶ B ──▶ E ──▶ F ◀── main │
│ │
│ After (git merge feature): │
│ ┌──▶ C ──▶ D ─────────┐ │
│ main ──▶ A ──▶ B ──▶ E ──▶ F ──▶ M ◀── main (Merge Commit)│
│ │
└─────────────────────────────────────────────────────────────┘
# Fast-forwardマージ(履歴が直線的)
git merge feature
# 常にマージコミットを作成(履歴を明確に)
git merge --no-ff feature
# マージコミットを作らない(できる場合のみ)
git merge --ff-only feature
Squash マージ
複数のコミットを1つにまとめてマージします。
# featureブランチの全コミットを1つにまとめてマージ
git merge --squash feature
git commit -m "feature: 新機能を追加"
┌──────────────────────────────────────────────────────────────────┐
│ Squash マージ │
│ │
│ Before: │
│ ┌──▶ C ──▶ D ──▶ E ◀── feature │
│ main ──▶ A ──▶ B │
│ │
│ After (git merge --squash feature): │
│ main ──▶ A ──▶ B ──▶ CDE' ◀── main (C+D+Eの変更を1コミットに)│
│ │
└──────────────────────────────────────────────────────────────────┘
Rebase マージ
履歴を直線的に保ちながらマージします。
# featureブランチをmainの先頭にリベース
git checkout feature
git rebase main
# その後、mainでFast-forwardマージ
git checkout main
git merge feature
┌─────────────────────────────────────────────────────────────┐
│ Rebase マージ │
│ │
│ Before: │
│ ┌──▶ C ──▶ D ◀── feature │
│ main ──▶ A ──▶ B ──▶ E ◀── main │
│ │
│ After (git rebase main): │
│ main ──▶ A ──▶ B ──▶ E ──▶ C' ──▶ D' ◀── feature │
│ ▲ │
│ └── main │
│ │
└─────────────────────────────────────────────────────────────┘
マージ戦略の選び方
| 戦略 | メリット | デメリット | 適したケース |
|---|---|---|---|
| merge --no-ff | 履歴が明確 | マージコミットが増える | 機能ブランチのマージ |
| merge --squash | 履歴がきれい | 詳細履歴が失われる | 小さな修正のマージ |
| rebase | 直線的な履歴 | コンフリクト解決が複雑 | 個人ブランチの更新 |
コンフリクトの解決
コンフリクトが発生する仕組み
同じファイルの同じ行を複数のブランチで変更した場合、コンフリクトが発生します。
# マージ時にコンフリクトが発生
git merge feature
# Auto-merging file.js
# CONFLICT (content): Merge conflict in file.js
# Automatic merge failed; fix conflicts and then commit the result.
# コンフリクトしているファイルを確認
git status
# Unmerged paths:
# both modified: file.js
コンフリクトマーカーの読み方
<<<<<< HEAD
// mainブランチの変更
const value = 100;
=======
// featureブランチの変更
const value = 200;
MERGE_END (example-branch)
| マーカー | 意味 |
|---|---|
<<<<<< HEAD |
現在のブランチ(マージ先)の変更の開始 |
======= |
変更の区切り |
MERGE_END (example-branch) |
マージ元ブランチの変更の終了 |
コンフリクト解決の手順
# コンフリクトしているファイルを編集
# 手動でマーカーを削除し、正しいコードを残す
# 解決済みとしてマーク
git add file.js
# マージを完了
git commit
# または
git merge --continue
# マージを中止する場合
git merge --abort
便利なコンフリクト解決ツール
# マージツールを使用
git mergetool
# ours/theirsで一括解決
git checkout --ours file.js # 現在のブランチの変更を採用
git checkout --theirs file.js # マージ元の変更を採用
# リベース時の場合
git rebase --continue # 解決後に続行
git rebase --abort # リベースを中止
git rebase --skip # 現在のコミットをスキップ
rerere(Reuse Recorded Resolution)
同じコンフリクトを自動的に解決してくれる機能です。
# rerereを有効化
git config --global rerere.enabled true
# 記録された解決策を確認
git rerere status
# 記録をクリア
git rerere forget file.js
git reset を完全理解する
3種類のreset
┌─────────────────────────────────────────────────────────────┐
│ │
│ [Working Dir] ◀─hard─ [Staging] ◀─soft/mixed─ [Repository]│
│ │
│ --soft : HEADだけ移動(ステージングと作業ディレクトリは維持)│
│ --mixed : HEADとステージングを移動(作業ディレクトリは維持) │
│ --hard : すべてを移動(変更がすべて失われる) │
│ │
└─────────────────────────────────────────────────────────────┘
# soft: コミットを取り消し、変更はステージング済みのまま
git reset --soft HEAD~1
# mixed(デフォルト): コミットを取り消し、変更は作業ディレクトリに
git reset HEAD~1
git reset --mixed HEAD~1
# hard: コミットと変更をすべて取り消し
git reset --hard HEAD~1
具体的な使用例
# 直前のコミットを取り消して修正したい
git reset --soft HEAD~1
# 修正して再コミット
git commit -m "修正後のコミット"
# ステージングを取り消したい
git reset HEAD file.js
# または
git restore --staged file.js # Git 2.23以降
# 作業ディレクトリの変更を取り消したい
git checkout -- file.js
# または
git restore file.js # Git 2.23以降
# 特定のコミットまで戻したい(変更は保持)
git reset --soft abc1234
# 完全にクリーンな状態に戻したい
git reset --hard origin/main
reset vs revert
| 操作 | reset | revert |
|---|---|---|
| 履歴 | 書き換える | 新しいコミットを追加 |
| プッシュ済み | 使用注意(force push必要) | 安全に使用可能 |
| チーム開発 | 非推奨 | 推奨 |
# revertで特定のコミットを取り消す(新しいコミットを作成)
git revert abc1234
# 複数のコミットをrevert
git revert HEAD~3..HEAD
# マージコミットをrevert
git revert -m 1 merge_commit_hash
トラブルシューティング
git reflog で失われたコミットを復元
間違ってリセットやリベースをしてしまった場合、reflogからコミットを復元できます。
# reflogを表示
git reflog
# 出力例
abc1234 HEAD@{0}: reset: moving to HEAD~3
def5678 HEAD@{1}: commit: 重要な変更
ghi9012 HEAD@{2}: commit: 機能追加
# 失われたコミットを復元
git reset --hard def5678
git bisect でバグの原因コミットを特定
二分探索でバグを導入したコミットを効率的に特定できます。
# bisectを開始
git bisect start
# 現在のコミットはバグあり
git bisect bad
# このコミットは正常だった
git bisect good v1.0.0
# Gitが自動的に中間のコミットをチェックアウト
# テストして結果を報告
git bisect good # または git bisect bad
# 原因が特定されたら終了
git bisect reset
git blame で変更履歴を追跡
特定の行が誰によっていつ変更されたかを確認できます。
# ファイル全体のblame
git blame filename.js
# 特定の行範囲
git blame -L 10,20 filename.js
# 空白の変更を無視
git blame -w filename.js
効率化のためのエイリアス設定
頻繁に使うコマンドはエイリアスを設定しましょう。
git config --global alias.co checkout
git config --global alias.br branch
git config --global alias.ci commit
git config --global alias.st status
git config --global alias.unstage 'reset HEAD --'
git config --global alias.last 'log -1 HEAD'
git config --global alias.visual '!gitk'
git config --global alias.graph 'log --oneline --graph --all'
.gitconfigファイルに直接記述することもできます:
[alias]
co = checkout
br = branch
ci = commit
st = status
lg = log --oneline --graph --decorate --all
undo = reset --soft HEAD~1
amend = commit --amend --no-edit
チーム開発で役立つテクニック
git fetch --prune でリモートの削除されたブランチを同期
リモートで削除されたブランチをローカルからも削除します。
git fetch --prune
# または自動的に実行されるよう設定
git config --global fetch.prune true
git log の高度な使い方
コミット履歴を様々な形式で表示できます。
# グラフ表示
git log --oneline --graph --all
# 特定のファイルの履歴
git log --follow -p filename.js
# 特定の期間
git log --after="2024-01-01" --before="2024-12-31"
# 特定の著者
git log --author="username"
# コミットメッセージで検索
git log --grep="バグ修正"
# 変更内容で検索
git log -S "function_name"
git diff の便利なオプション
# 単語単位での差分
git diff --word-diff
# 変更されたファイル名のみ表示
git diff --name-only
# 統計情報を表示
git diff --stat
# ステージングされた変更を表示
git diff --staged
.gitignore のグローバル設定
プロジェクト共通で無視したいファイルはグローバルに設定できます。
git config --global core.excludesfile ~/.gitignore_global
~/.gitignore_globalの例:
# OS
.DS_Store
Thumbs.db
# エディタ
*.swp
*.swo
.idea/
.vscode/
# 依存関係
node_modules/
__pycache__/
git hooks で自動化
.git/hooks/ディレクトリにスクリプトを配置することで、特定のタイミングで自動的に処理を実行できます。
pre-commitフックの例:
#!/bin/sh
# コミット前にlintを実行
npm run lint
if [ $? -ne 0 ]; then
echo "Lintエラーがあります。修正してからコミットしてください。"
exit 1
fi
commit-msgフックでコミットメッセージのフォーマットを強制:
#!/bin/sh
# コミットメッセージが規約に沿っているか確認
commit_regex='^(feat|fix|docs|style|refactor|test|chore)(\(.+\))?: .{1,50}'
if ! grep -qE "$commit_regex" "$1"; then
echo "コミットメッセージが規約に沿っていません"
echo "例: feat(auth): ログイン機能を追加"
exit 1
fi
チーム開発のベストプラクティス
ブランチ戦略
Git Flow
大規模プロジェクト向けの戦略です。
┌─────────────────────────────────────────────────────────────┐
│ Git Flow │
│ │
│ main ───●─────────────────●─────────────────●───▶ │
│ ↑ ↑ ↑ │
│ hotfix ──┴─●─────────────────┘ │ │
│ │ │
│ release ─────────────●───────●──────────────────┘ │
│ ↑ │ │
│ develop ───●───●───●─┴───●───┴───●───●───▶ │
│ ↑ ↑ ↑ ↑ ↑ │
│ feature ─┴───┴───┘ └───┘ │
│ │
└─────────────────────────────────────────────────────────────┘
| ブランチ | 用途 |
|---|---|
| main | 本番リリース |
| develop | 開発統合 |
| feature/* | 機能開発 |
| release/* | リリース準備 |
| hotfix/* | 緊急修正 |
GitHub Flow
シンプルで継続的デプロイ向けの戦略です。
┌─────────────────────────────────────────────────────────────┐
│ GitHub Flow │
│ │
│ main ───●───────●───────●───────●───▶ │
│ ↑ ↑ ↑ ↑ │
│ feature ─┴─●───●───┘ │ │ │
│ │ │ │
│ feature ───────────────────┴───●───┘ │
│ │
└─────────────────────────────────────────────────────────────┘
ルール:
- mainは常にデプロイ可能
- 作業はfeatureブランチで
- プルリクエストでレビュー
- レビュー後にmainへマージ
- mainへのマージ後すぐにデプロイ
Trunk Based Development
小さなチーム向けの戦略です。
┌─────────────────────────────────────────────────────────────┐
│ Trunk Based Development │
│ │
│ trunk(main) ───●───●───●───●───●───●───●───▶ │
│ ↑ ↑ ↑ ↑ │
│ short-lived ───┴───┘ └───┘ │
│ branches │
│ │
└─────────────────────────────────────────────────────────────┘
特徴:
- ブランチは1-2日で完了
- フィーチャーフラグで未完成機能を隠す
- 頻繁な小さなコミット
コミットメッセージの規約
Conventional Commits
<type>(<scope>): <subject>
<body>
<footer>
| type | 用途 |
|---|---|
| feat | 新機能 |
| fix | バグ修正 |
| docs | ドキュメント |
| style | フォーマット変更 |
| refactor | リファクタリング |
| test | テスト追加・修正 |
| chore | ビルド・ツール関連 |
| perf | パフォーマンス改善 |
# 例
feat(auth): ログイン機能を追加
- メールとパスワードでの認証
- JWTトークンの発行
- セッション管理
Closes #123
プルリクエストのベストプラクティス
# featureブランチを最新のmainにリベース
git fetch origin
git rebase origin/main
# コンフリクトを解決してプッシュ
git push --force-with-lease origin feature/my-feature
--force-with-lease について
--forceより安全なオプションです。他の人がプッシュした変更がある場合、拒否されます。
# 通常のforce push(危険)
git push --force origin feature/my-feature
# 安全なforce push(他の変更があれば失敗)
git push --force-with-lease origin feature/my-feature
大きなファイルの管理(Git LFS)
バイナリファイルや大きなファイルを効率的に管理します。
# Git LFSのインストール
brew install git-lfs # macOS
apt install git-lfs # Ubuntu
# リポジトリでLFSを有効化
git lfs install
# 追跡するファイルパターンを指定
git lfs track "*.psd"
git lfs track "*.zip"
git lfs track "assets/**"
# .gitattributesをコミット
git add .gitattributes
git commit -m "chore: Git LFSを設定"
# LFS管理ファイルの確認
git lfs ls-files
セキュリティ:機密情報の管理
# 誤ってコミットした機密情報を履歴から削除
# git-filter-repo を使用(推奨)
pip install git-filter-repo
git filter-repo --path secrets.txt --invert-paths
# BFG Repo-Cleaner を使用
java -jar bfg.jar --delete-files secrets.txt
java -jar bfg.jar --replace-text passwords.txt
# 削除後は強制プッシュが必要
git push --force --all
予防策
# pre-commit hookで機密情報をチェック
# .git/hooks/pre-commit
#!/bin/sh
if git diff --cached | grep -E "(password|secret|api_key)\s*=" > /dev/null; then
echo "警告: 機密情報がコミットに含まれている可能性があります"
exit 1
fi
まとめ
この記事では、現場で即戦力となるGitテクニックを紹介しました。
基本操作
| テクニック | 用途 |
|---|---|
| git add -p | 部分的なステージング |
| git stash | 作業の一時保存 |
| git commit --amend | 直前のコミット修正 |
ブランチ操作
| テクニック | 用途 |
|---|---|
| git rebase -i | コミット履歴の整理 |
| git cherry-pick | 特定コミットの適用 |
| git worktree | 複数ブランチ同時作業 |
マージとコンフリクト
| テクニック | 用途 |
|---|---|
| merge --no-ff | 履歴を明確にするマージ |
| merge --squash | コミットをまとめてマージ |
| rebase | 直線的な履歴を維持 |
| rerere | コンフリクト解決の記録・再利用 |
トラブルシューティング
| テクニック | 用途 |
|---|---|
| git reflog | 失われたコミットの復元 |
| git bisect | バグ原因の特定 |
| git blame | 変更履歴の追跡 |
| git reset | コミットの取り消し |
| git revert | 安全なコミット取り消し |
効率化
| テクニック | 用途 |
|---|---|
| エイリアス | コマンドの効率化 |
| git fetch --prune | リモート同期 |
| git log オプション | 履歴の高度な検索 |
| git diff オプション | 差分の詳細表示 |
| グローバル .gitignore | 共通ファイルの除外 |
| git hooks | 処理の自動化 |
チーム開発
| テクニック | 用途 |
|---|---|
| Git Flow / GitHub Flow | ブランチ戦略 |
| Conventional Commits | コミットメッセージ規約 |
| --force-with-lease | 安全なforce push |
| Git LFS | 大きなファイルの管理 |
これらのテクニックを日々の開発に取り入れることで、生産性が大幅に向上するはずです。特に、Gitの内部構造やマージ戦略を理解することで、トラブル発生時にも冷静に対処できるようになります。
ぜひ一つずつ試してみてください!