株式会社Good Labでエンジニアをしている コータロー です。
日々、Java・SQL・Gitなどの技術情報や、新人エンジニア向けの学習ノウハウ、
AI活用についての情報を発信しています。
Good Labについて気になった方は、コーポレートサイトもぜひご覧ください。
▶コーポレートサイト
はじめに
この記事は 「図解でわかるGitの仕組み」シリーズ の第7回です。
Gitの内部構造を、1テーマ1記事で図解していきます。
今回は stash、cherry-pick、rebase -i の3つを取り上げます。
いずれも日常の add / commit / push とは別枠で覚えるコマンドですが、実務では「知っているかどうか」で作業効率が大きく変わります。
- 作業を中断して別ブランチに移りたい → stash
- 特定のコミットだけ別ブランチに持っていきたい → cherry-pick
- push 前にコミット履歴をきれいに整理したい → rebase -i
本記事では、それぞれの仕組みを図で示しながら、実務での使いどころを解説します。
対象読者
-
add/commit/push/branch/mergeの基本操作を知っている方 - 「stash って何?」「cherry-pick ってどういうとき使うの?」と疑問に感じている方
- コミット履歴をきれいに整理してから PR を出したい方
1. git stash ― 作業を一時退避する
1-1. stash とは何か
git stash は、ワークツリーとステージングエリア(インデックス)の変更を一時的に退避させ、ワークツリーを HEAD の状態に戻すコマンドです。
「作業中だけど、急ぎで別ブランチの対応が必要になった」という場面で使います。
【図解】stash の仕組み
ワークツリー stash スタック
┌──────────────┐ ┌────────────────┐
│ 変更A(未add) │──stash──→│ stash@{0} │
│ 変更B(add済み) │ │ 変更A + 変更B │
└──────────────┘ ├────────────────┤
↓ │ stash@{1} │
HEAD と同じ状態に戻る │ (以前の退避) │
└────────────────┘
※ stash はスタック構造(LIFO: 後入れ先出し)
※ 最新の退避が stash@{0}
補足: stash は内部的にはコミットオブジェクトとして保存されます。ブランチには属さない特殊なコミットです。
1-2. 基本操作
# 現在の変更を退避(追跡済みファイルのみ)
git stash
# メッセージ付きで退避(後で識別しやすい)
git stash push -m "ユーザー画面のレイアウト修正中"
# 未追跡ファイル(新規作成ファイル)も含めて退避
git stash push -u
# 退避した一覧を表示
git stash list
git stash list の出力例:
stash@{0}: On feature/user: ユーザー画面のレイアウト修正中
stash@{1}: WIP on main: abc1234 前回の退避
1-3. 退避した変更を復元する
# 最新の stash を復元し、スタックから削除
git stash pop
# 最新の stash を復元するが、スタックには残す
git stash apply
# 特定の stash を指定して復元(スタックには残す)
git stash apply stash@{1}
# 特定の stash をスタックから削除
git stash drop stash@{1}
# すべての stash を削除
git stash clear
【図解】pop と apply の違い
pop:
stash@{0} ──復元──→ ワークツリー
↓
スタックから削除
apply:
stash@{0} ──復元──→ ワークツリー
↓
スタックに残ったまま(何度でも apply 可能)
1-4. 実務ユースケース
ケース1: 緊急バグ対応
# feature ブランチで作業中に、本番バグの報告が来た
git stash push -m "feature/user 作業途中"
# main ブランチに切り替えてバグ修正
git switch main
git switch -c hotfix/login-error
# ... バグ修正 & コミット & push ...
# 元のブランチに戻って作業再開
git switch feature/user
git stash pop
ケース2: 別ブランチに変更を持っていきたい
「本来 feature/A で作業すべき変更を main で書き始めてしまった」という場合:
# main での変更を退避
git stash
# 正しいブランチに切り替えて復元
git switch feature/A
git stash pop
1-5. stash の注意点
| 注意点 | 説明 |
|---|---|
| 未追跡ファイル | デフォルトでは退避されない。-u オプションが必要 |
| コンフリクト |
pop / apply 時にコンフリクトが発生する場合がある。その場合は手動で解消し、git stash drop で手動削除する |
| 長期保存には不向き | stash は一時的な退避手段。長期間残すなら別ブランチにコミットする方が安全 |
2. git cherry-pick ― 特定のコミットだけ持ってくる
2-1. cherry-pick とは何か
git cherry-pick は、指定したコミットの変更内容を、現在のブランチに新しいコミットとして適用するコマンドです。
ブランチ全体をマージするのではなく、「このコミットだけほしい」というときに使います。
【図解】cherry-pick の仕組み
main ブランチ:
A ── B ── C ── D
↑ HEAD
feature ブランチ:
A ── B ── E ── F ── G
↑ 「F だけ main にほしい」
git cherry-pick F を実行すると:
main ブランチ:
A ── B ── C ── D ── F'
↑ HEAD
F と同じ変更内容の
新しいコミット(ハッシュは異なる)
※ F' は F のコピー。コミットハッシュは別物になる
※ feature ブランチの F はそのまま残る
2-2. 基本操作
# コミットハッシュを確認
git log --oneline feature/user
# 出力例:
# a7b8c9d ユーザー削除機能を追加
# d4e5f6a バリデーションを修正 ← これだけほしい
# a1b2c3d ユーザー登録画面を作成
# 現在のブランチ(main)に特定のコミットを適用
git cherry-pick d4e5f6a
2-3. よく使うオプション
# コミットせずに変更だけ適用(ステージングまで)
git cherry-pick --no-commit d4e5f6a
# 複数のコミットを一度に適用
git cherry-pick d4e5f6a a7b8c9d
# 連続するコミットを範囲指定で適用(始点は含まない)
git cherry-pick a1b2c3d..a7b8c9d
# → d4e5f6a, a7b8c9d の 2 つが適用される(a1b2c3d は含まない)
注意: 範囲指定
A..Bは A 自体を含みません。A を含めたい場合はA^..Bと書きます。
2-4. コンフリクトが発生した場合
# cherry-pick 中にコンフリクトが発生
git cherry-pick d4e5f6a
# CONFLICT (content): ...
# 1. コンフリクトを手動で解消
# 2. 解消したファイルを add
git add src/UserService.java
# 3. cherry-pick を続行
git cherry-pick --continue
# もしくは中止
git cherry-pick --abort
2-5. 実務ユースケース
ケース1: バグ修正を複数ブランチに適用
# develop ブランチでバグ修正をコミット済み
# そのバグ修正を release ブランチにも適用したい
git switch release/v2.0
git cherry-pick abc1234
ケース2: 間違えたブランチでのコミットを移動
# main に直接コミットしてしまった変更を feature に移す
# 1. main の最新コミットのハッシュを控える
git log --oneline -1
# abc1234 間違えてコミットした変更
# 2. feature ブランチに cherry-pick
git switch feature/user
git cherry-pick abc1234
# 3. main のコミットを取り消す
git switch main
git reset --soft HEAD~1
git restore --staged .
2-6. cherry-pick の注意点
| 注意点 | 説明 |
|---|---|
| コミットの重複 | cherry-pick で作られるコミットは元と別のハッシュを持つ。同じ変更が2つのブランチに別コミットとして存在するため、後でマージ時にコンフリクトする可能性がある |
| マージコミット | マージコミットの cherry-pick には -m オプションで親番号の指定が必要(通常は -m 1) |
| 多用は避ける | cherry-pick の多用は履歴を複雑にする。ブランチ戦略の見直しが根本解決になる場合が多い |
3. git rebase -i ― コミット履歴を整理する
3-1. rebase -i とは何か
git rebase -i(interactive rebase)は、指定した範囲のコミットを対話的に編集できるコマンドです。コミットの順序変更、統合(squash)、メッセージ修正、削除などが可能です。
push 前にコミット履歴を整理して、レビューしやすい PR を作るために使います。
【図解】rebase -i の概念
整理前:
A ── B ── C ── D ── E (feature ブランチ)
│ │ │
typo修正 WIP 完成
git rebase -i HEAD~3 で整理:
整理後:
A ── B ── CDE' (feature ブランチ)
│
C + D + E を1つにまとめた
コミット(squash)
※ コミットハッシュは変わる(履歴の書き換え)
3-2. 基本操作
# 直近3コミットを対話的に編集
git rebase -i HEAD~3
実行するとエディタが開き、以下のような画面が表示されます:
pick a1b2c3d ユーザー登録画面を作成
pick d4e5f6a typo を修正
pick a7b8c9d バリデーションを追加
# Rebase onto ...
#
# Commands:
# p, pick = コミットをそのまま使う
# r, reword = コミットメッセージを変更
# e, edit = コミットを修正(一度停止)
# s, squash = 直前のコミットと統合(メッセージ編集あり)
# f, fixup = 直前のコミットと統合(メッセージは破棄)
# d, drop = コミットを削除
3-3. 主要コマンド一覧
| コマンド | 動作 | 使いどころ |
|---|---|---|
pick |
そのまま残す | デフォルト。変更不要なコミット |
reword |
メッセージだけ変更 | typo 修正やメッセージ改善 |
edit |
コミットを修正 | ファイルの追加・変更内容の修正 |
squash |
直前のコミットと統合 | 関連コミットをまとめる(メッセージ編集可) |
fixup |
直前のコミットと統合 | 関連コミットをまとめる(メッセージは直前のものを使用) |
drop |
コミットを削除 | 不要なコミットの除去 |
3-4. 操作例: squash でコミットをまとめる
作業中の細かいコミットを、PR 前に1つにまとめる例です。
git log --oneline -4
# a7b8c9d バリデーションのテストを追加
# d4e5f6a バリデーションを実装
# a1b2c3d typo を修正
# x9y8z7w ユーザー登録画面を作成
git rebase -i HEAD~3
エディタで以下のように編集します:
pick a1b2c3d typo を修正
squash d4e5f6a バリデーションを実装
squash a7b8c9d バリデーションのテストを追加
保存して閉じると、メッセージ編集画面が開きます:
# This is a combination of 3 commits.
# 1st commit message:
typo を修正
# 2nd commit message:
バリデーションを実装
# 3rd commit message:
バリデーションのテストを追加
メッセージを書き換えて保存します:
ユーザー登録にバリデーションを追加
【図解】squash の結果
Before:
x9y8z7w ── a1b2c3d ── d4e5f6a ── a7b8c9d
typo修正 実装 テスト
After:
x9y8z7w ── abc1234
ユーザー登録にバリデーションを追加
(3つのコミットが1つに統合された)
3-5. 操作例: reword でメッセージ修正
git rebase -i HEAD~2
reword d4e5f6a 修正 ← pick を reword に変更
pick a7b8c9d テストを追加
保存すると、コミットメッセージの編集画面が順に開きます。
3-6. 操作例: コミットの順序を入れ替える
エディタで行の順序を入れ替えるだけです。
# Before(エディタ上の表示は古い順 = 上が古い)
pick a1b2c3d 機能Aを実装
pick d4e5f6a 機能Bを実装
# After(行を入れ替える)
pick d4e5f6a 機能Bを実装
pick a1b2c3d 機能Aを実装
注意: 入れ替えるコミット同士が同じファイルを変更している場合、コンフリクトが発生する可能性があります。
3-7. コンフリクト発生時の対処
# rebase 中にコンフリクトが発生した場合
# 1. コンフリクトを手動で解消
# 2. 解消したファイルを add
git add src/UserService.java
# 3. rebase を続行
git rebase --continue
# もしくは中止(rebase 前の状態に戻る)
git rebase --abort
3-8. 実務ユースケース
ケース1: PR 前のコミット整理
# feature ブランチの全コミットを整理
# main から分岐した後のコミットが対象
git rebase -i main
ケース2: fixup でデバッグコミットを消す
git log --oneline -3
# a7b8c9d デバッグ用の console.log を削除
# d4e5f6a 検索機能を実装
# a1b2c3d ページネーションを追加
git rebase -i HEAD~2
pick d4e5f6a 検索機能を実装
fixup a7b8c9d デバッグ用の console.log を削除
結果: 「検索機能を実装」のコミットにデバッグ削除が吸収され、メッセージはそのまま残ります。
3-9. rebase -i の注意点
| 注意点 | 説明 |
|---|---|
| push 済みコミットには使わない |
rebase -i は履歴を書き換えるため、push 済みのコミットに使うと他の開発者と履歴が食い違う。force push が必要になり危険 |
| コミットハッシュが変わる | 編集したコミット以降のハッシュがすべて変わる |
| 共有ブランチでは禁止 | main や develop など、他の人が参照するブランチでは使わない |
--abort で戻れる |
操作を間違えた場合は git rebase --abort で元の状態に戻せる |
4. 3つのコマンドの使い分け
| コマンド | 目的 | 典型的な場面 |
|---|---|---|
git stash |
作業の一時退避 | 別ブランチへの緊急切り替え、ブランチ間の変更移動 |
git cherry-pick |
特定コミットの適用 | バグ修正の横展開、誤ブランチへのコミット移動 |
git rebase -i |
コミット履歴の整理 | PR 前の履歴クリーンアップ、WIP コミットの統合 |
【図解】3つのコマンドの位置づけ
┌──────────────────────────────────────────────────┐
│ Git ワークフロー │
│ │
│ 作業中 ──stash──→ 一時退避 ──stash pop──→ 作業再開 │
│ │
│ ブランチA ──cherry-pick──→ 特定コミットをブランチBへ │
│ │
│ push前 ──rebase -i──→ 履歴を整理 ──→ きれいな PR │
└──────────────────────────────────────────────────┘
5. よくある質問
Q1. stash と新しいブランチにコミット、どちらがよい?
短時間(数時間以内)の中断なら stash、長期間なら別ブランチにコミットする方が安全です。stash は「名前のないコミット」なので、何の作業だったか忘れやすくなります。git stash push -m "説明" でメッセージを付ける習慣をつけましょう。
Q2. cherry-pick と merge の違いは?
merge はブランチの全変更を統合します。cherry-pick は指定したコミットの変更だけを適用します。「1つのバグ修正だけほしい」なら cherry-pick、「ブランチ全体を取り込みたい」なら merge です。
Q3. rebase -i で間違えたらどうする?
git rebase --abort で rebase 開始前の状態に戻れます。また、git reflog で rebase 前のコミットハッシュを探して git reset --hard <ハッシュ> で復元することも可能です。
まとめ
| コマンド | 一言まとめ |
|---|---|
git stash |
変更を一時退避。pop で復元、apply でコピー復元 |
git stash push -u -m "説明" |
未追跡ファイルも含めてメッセージ付きで退避 |
git cherry-pick <hash> |
特定コミットだけ現在のブランチに適用 |
git cherry-pick --no-commit |
変更をステージングまでにとどめる |
git rebase -i HEAD~N |
直近 N コミットを対話的に編集 |
squash / fixup
|
コミットを統合(メッセージ編集あり/なし) |
reword |
コミットメッセージだけ変更 |
drop |
コミットを削除 |
どのコマンドも push 前のローカルコミットに対して使う のが安全な原則です。push 済みのコミットに対して使う場合は、force push が必要になるリスクを理解した上で慎重に行いましょう。
次回予告
第8回: .gitignore・フック・設定 ― Git環境を整備する
.gitignore の書き方、Git フック(pre-commit, commit-msg 等)による自動チェック、git config によるグローバル/ローカル設定を解説します。
シリーズ目次
| # | テーマ |
|---|---|
| 1 | Gitの全体像 ― ワークツリー・ステージ・リポジトリの3層構造 |
| 2 | ブランチとHEAD ― 「枝分かれ」の正体を理解する |
| 3 | マージとリベース ― 統合戦略の違いを図で比較 |
| 4 | リモートとローカル ― push/pull/fetchの裏側 |
| 5 | コンフリクト解消 ― なぜ起きるのか、どう直すのか |
| 6 | reset/revert/checkout ― 「戻す」操作を正しく使い分ける |
| 7 | stash/cherry-pick/rebase -i ― 実務で頼れる便利コマンド(この記事) |
| 8 | .gitignore・フック・設定 ― Gitをカスタマイズする |
| 9 | Git Flow vs GitHub Flow ― ブランチ戦略の選び方 |
| 10 | トラブルシューティング ― 「やらかした!」を救うコマンド集 |
参考
- Git 公式ドキュメント - git-stash
- Git 公式ドキュメント - git-cherry-pick
- Git 公式ドキュメント - git-rebase
- Pro Git Book - Stashing and Cleaning(英語)
- Pro Git Book - Rewriting History(英語)
「この記事が役に立った」と思ったら、LGTM とストックをお願いします。
@kotaro_ai_lab
AI活用や開発効率化について発信しています。フォローお気軽にどうぞ!