0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

【図解でわかるGit 第7回】stash/cherry-pick/rebase -i ― 実務で頼れる便利コマンド

0
Posted at

株式会社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 トラブルシューティング ― 「やらかした!」を救うコマンド集

参考


「この記事が役に立った」と思ったら、LGTM とストックをお願いします。

@kotaro_ai_lab
AI活用や開発効率化について発信しています。フォローお気軽にどうぞ!

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?