はじめに
git mergeとgit rebaseはどちらも一方のブランチから別のブランチに変更を統合しますが、根本的に異なる方法で行います。それぞれをいつ使うべきかを理解することは、クリーンで有用なGit履歴を維持するために不可欠です。
この記事では、両者の違い、ユースケース、ベストプラクティスを説明します。
根本的な違い
gitGraph
commit id: "A"
commit id: "B"
branch feature
commit id: "C"
commit id: "D"
checkout main
commit id: "E"
Merge後
gitGraph
commit id: "A"
commit id: "B"
branch feature
commit id: "C"
commit id: "D"
checkout main
commit id: "E"
merge feature id: "M" type: HIGHLIGHT
Rebase + Fast-Forward後
gitGraph
commit id: "A"
commit id: "B"
commit id: "E"
commit id: "C'"
commit id: "D'"
Git Merge
動作原理
Mergeは2つのブランチを組み合わせる新しいコミットを作成します:
# featureブランチでmainをマージ
git checkout feature
git merge main
# またはfeatureをmainにマージ
git checkout main
git merge feature
Mergeの種類
| 種類 | 条件 | 結果 |
|---|---|---|
| Fast-forward | 分岐なし | 線形履歴 |
| Three-way | ブランチが分岐 | マージコミット |
| Squash | コミットを結合 | 単一の新しいコミット |
Fast-Forward Merge
# featureがmainより先に進んでいて分岐がない場合
git checkout main
git merge feature
# マージコミットは作成されない
Three-Way Merge
# 両方のブランチに新しいコミットがある場合
git checkout main
git merge feature
# マージコミットが作成される
Squash Merge
# featureのすべてのコミットを1つに結合
git checkout main
git merge --squash feature
git commit -m "Add feature X"
Git Rebase
動作原理
Rebaseはあなたのコミットを別のブランチの上に再適用します:
# featureブランチでmainにrebase
git checkout feature
git rebase main
何が起こるか
- Gitが共通の祖先を見つける
- あなたのコミットをパッチとして保存
- ターゲットブランチにリセット
- あなたのコミットを1つずつ適用
# rebase前
main: A - B - E
feature: A - B - C - D
# rebase後
main: A - B - E
feature: A - B - E - C' - D'
Interactive Rebase
コミットを修正、結合、または並べ替え:
git rebase -i HEAD~3
# または
git rebase -i main
エディタで:
pick abc123 Add login form
squash def456 Fix typo in login
pick ghi789 Add validation
# コマンド:
# p, pick = コミットを使用
# r, reword = コミットを使用するがメッセージを編集
# e, edit = コミットを使用するが修正のために停止
# s, squash = コミットを使用するが前のものに統合
# f, fixup = squashと同様だがメッセージを破棄
# d, drop = コミットを削除
比較
| 観点 | Merge | Rebase |
|---|---|---|
| 履歴 | 完全な履歴を保持 | 線形履歴を作成 |
| マージコミット | あり(three-wayの場合) | なし |
| 元のコミット | 保持される | 書き換えられる(新しいSHA) |
| 共有ブランチに安全 | はい | いいえ |
| コンフリクト解決 | 1回 | コミットごと |
Mergeを使うべき時
1. 共有ブランチの統合
# 共有ブランチをfeatureにマージ
git checkout feature
git merge main
# 安全:履歴を書き換えない
2. 履歴の保持
# 機能がいつ統合されたかを追跡する必要がある場合
git merge --no-ff feature
# 常にマージコミットを作成
3. プルリクエストの完了
# GitHub/GitLabのデフォルト動作
# トレーサビリティのためにマージコミットを作成
マージコミットメッセージ
Merge branch 'feature/user-auth' into main
* feature/user-auth:
Add password validation
Implement login form
Create user model
Rebaseを使うべき時
1. featureブランチの更新
# PR前にmainから最新の変更を取得
git checkout feature
git rebase main
git push --force-with-lease
2. ローカルコミットの整理
# プッシュ前にWIPコミットをsquash
git rebase -i HEAD~5
3. 線形履歴の維持
# 線形履歴を好むプロジェクト向け
git checkout main
git rebase feature
# その後fast-forwardマージ
ゴールデンルール
共有ブランチにプッシュされたコミットは決してrebaseしない。
flowchart TD
A["コミットはプッシュ済み?"] -->|いいえ| B["自由にRebase"]
A -->|はい| C["他の人がブランチを使用中?"]
C -->|いいえ| D["慎重にRebase"]
C -->|はい| E["Mergeのみ使用"]
style B fill:#22c55e,color:#fff
style D fill:#f59e0b,color:#fff
style E fill:#ef4444,color:#fff
コンフリクトの処理
Mergeのコンフリクト
git merge feature
# CONFLICT in file.txt
# file.txtを編集して解決
git add file.txt
git commit # マージを完了
Rebaseのコンフリクト
git rebase main
# CONFLICT in file.txt
# file.txtを編集して解決
git add file.txt
git rebase --continue # 次のコミットへ
# または完全に中止
git rebase --abort
コンフリクトマーカー
<<<<<<< HEAD
現在のブランチの内容
=======
入ってくるブランチの内容
>>>>>>> feature
実践的なワークフロー
ワークフロー1: Merge前にRebase
# 1. featureを最新のmainで更新
git checkout feature
git fetch origin
git rebase origin/main
# 2. 更新したブランチをプッシュ
git push --force-with-lease
# 3. PRを作成またはマージ
git checkout main
git merge feature
ワークフロー2: Squash and Merge
# GitHub PRでよく使われる
# PRのすべてのコミットを1つに結合
git checkout main
git merge --squash feature
git commit -m "Add user authentication (#123)"
ワークフロー3: クリーンな履歴のためのInteractive Rebase
# PR作成前にコミットを整理
git rebase -i main
# エディタで:
pick abc123 Add user model
squash def456 WIP: user model
squash ghi789 Fix user model typo
pick jkl012 Add authentication service
fixup mno345 Fix auth bug
# 結果: クリーンで論理的なコミット
安全にForce Push
rebaseすると、force pushが必要になります:
# 危険: チェックなしでリモートを上書き
git push --force
# 安全: リモートに新しいコミットがあると失敗
git push --force-with-lease
# より安全: 期待するリモート状態を指定
git push --force-with-lease=origin/feature
Rebase戦略
マージコミットの保持
# rebase中にマージコミットを保持
git rebase --rebase-merges main
Autosquash
# squash用にコミットをマーク
git commit --fixup=abc123
git commit --squash=def456
# interactive rebaseで自動配置
git rebase -i --autosquash main
ベストプラクティス
1. チームの規約を確立
チームのアプローチをドキュメント化:
## Gitワークフロー
### featureブランチ
- PR作成前にmainにrebase
- WIPコミットをinteractive rebaseでsquash
### プルリクエスト
- 単一の論理的な変更にはsquash mergeを使用
- 大きな機能にはmerge commitを使用
### 禁止事項
- mainやdevelopにforce push
- 共有ブランチをrebase
2. コミットを小さく焦点を絞って
# 良い: 各コミットがアトミック
git commit -m "Add User model"
git commit -m "Add UserRepository"
git commit -m "Add UserController"
# 悪い: 大きく焦点がぼやけたコミット
git commit -m "Add user functionality"
3. 良いコミットメッセージを書く
Add user authentication with JWT
- Implement login/logout endpoints
- Add JWT token generation
- Include refresh token rotation
- Add rate limiting for login attempts
Closes #123
4. よく使う操作にエイリアスを使用
# .gitconfig
[alias]
rb = rebase
rbi = rebase -i
rbc = rebase --continue
rba = rebase --abort
fpush = push --force-with-lease
決定マトリックス
| シナリオ | 推奨 |
|---|---|
| ローカルブランチをmainから更新 | Rebase |
| featureをmainにマージ | Merge(またはsquash merge) |
| 共有ブランチ | Mergeのみ |
| ローカルコミットの整理 | Interactive rebase |
| 完全な履歴の保持 | Merge with --no-ff |
| 線形履歴が必要 | Rebase + fast-forward |
まとめ
| コマンド | 使用するタイミング | 結果 |
|---|---|---|
git merge |
共有ブランチの統合 | 履歴を保持、マージコミット |
git merge --squash |
クリーンな単一コミット統合 | 1つの新しいコミット |
git rebase |
ローカルブランチの更新 | 線形履歴、新しいコミット |
git rebase -i |
コミットの整理 | 変更されたコミット履歴 |
mergeとrebaseはどちらも不可欠なツールです。重要なのは、それぞれがいつ適切かを知り、チーム内で一貫性を維持することです。
参考資料
- O'Reilly - Version Control with Git, Chapters 6, 9
- Packt - DevOps Unleashed with Git and GitHub, Chapter 3
- Atlassian - Merging vs Rebasing