知っていると差がつく Git テクニック集:stash から reflog まで
よるほろぐ編集部
add・commit・branch・rebase といった基本操作には慣れてきたけれど、「stash って使いどころがよくわからない」「reset したらコミットが消えた気がする」「どのコミットでバグが入ったか調べたい」——そんな場面で助かるのが、Git の中級テクニックです。
この記事では、日常の作業効率を上げるコマンドと、やってしまった操作から復帰するための救済技をまとめます。各機能は「どんな状況で使うか」を先に示してからコマンド例と説明を続けます。
git stash:作業を一時退避する
状況: ブランチを切り替えたいが、まだコミットできる状態ではない作業途中の変更がある。
# 変更をスタッシュに退避し、作業ツリーをきれいにする
git stash
これだけで、ステージ済み・未ステージの変更(ただしトラッキング済みのファイルのみ)が退避され、作業ツリーが HEAD と一致した状態に戻ります。
未追跡ファイルも含める
# -u で untracked files(git add していないファイル)もまとめて退避
git stash -u
名前をつけて退避する
# -m でメッセージをつけると、あとで見分けやすくなる
git stash push -m "WIP: ログイン画面のバリデーション追加"
git stash だけでも動きますが、複数スタッシュが溜まるとどれが何かわからなくなります。名前付きのほうが安心です。
スタッシュの一覧・確認・復元
# 一覧を表示(stash@{0} が最新)
git stash list
# stash@{0}: On feature/login: WIP: ログイン画面のバリデーション追加
# stash@{1}: WIP on main: 9cc0589 ...
# 差分を確認する(-p でパッチ形式)
git stash show -p stash@{0}
# 最新のスタッシュを適用してリストから削除
git stash pop
# 適用するがリストには残す(複数ブランチに同じ変更を当てたいとき)
git stash apply stash@{1}
# 不要になったスタッシュを個別に削除
git stash drop stash@{1}
特定ファイルだけ退避する
# src/auth.ts だけをスタッシュに入れる
git stash push -- src/auth.ts
一部のファイルだけ退避して残りの変更は作業ツリーに置いておきたい場合に便利です。
git switch / restore:checkout の役割を分担する
git checkout はブランチ切替・ファイル復元・デタッチ HEAD と役割が多く、コマンドの意味が文脈依存で把握しにくいという歴史がありました。Git 2.23 からは switch と restore が追加され、それぞれの役割が明確になっています。
ブランチを切り替える:git switch
# main ブランチに切り替える
git switch main
# 新しいブランチを作って切り替える(checkout -b と同等)
git switch -c feature/add-search
# ひとつ前にいたブランチに戻る
git switch -
ファイルを戻す:git restore
状況: 編集途中のファイルを、コミット済みの状態に戻したい。
# 作業ツリーのファイルを HEAD の状態に戻す(編集内容が消えるので注意)
git restore src/auth.ts
# 別のコミットやブランチの内容を持ってくる
git restore --source=main -- config/settings.py
状況: git add してしまったファイルをステージから外したい(内容は保持する)。
# ステージングを取り消す(インデックスを HEAD に戻す)
git restore --staged src/auth.ts
git add の逆操作です。--staged をつけると作業ツリーはそのままで、インデックス(ステージング)だけが HEAD の状態に戻ります。
git reflog:消えたように見えるコミットを救う
状況: git reset --hard や git rebase の後、「あのコミットが消えた!」と気づいた。
Git は HEAD の移動履歴を reflog として記録しています。reset --hard でコミットツリーから外れたコミットも、reflog を使えば一定期間内なら見つけられます。
# HEAD の移動履歴を表示(最新が上)
git reflog
# a3f9c12 HEAD@{0}: reset: moving to HEAD~2
# 8b2e4a1 HEAD@{1}: commit: ログインボタンのスタイルを修正
# 4d1c763 HEAD@{2}: commit: 認証フローを実装
# ...
目当てのコミットハッシュが見つかったら、そこにブランチを向ければ復帰完了です。
# 新しいブランチを作って復元する(安全)
git branch recover/login-style 8b2e4a1
# または HEAD をそのハッシュに移動する
git reset --hard 8b2e4a1
reflog のエントリは、デフォルトで 90 日(到達不能なコミットは 30 日)後に削除されます(gc.reflogExpire / gc.reflogExpireUnreachable で設定可能)。「消した」と気づいたらなるべく早く reflog を確認してください。
reflog の活用パターン
# rebase 前の状態に戻りたいとき
git reflog
# 3c8a012 HEAD@{0}: rebase (finish): returning to refs/heads/feature/login
# a7b3ef5 HEAD@{3}: commit: 認証フローを実装(rebase前)
git reset --hard a7b3ef5
git bisect:不具合の混入コミットを二分探索で見つける
状況: ある時点まで動いていた機能が壊れている。どのコミットで壊れたか特定したい。
git bisect は「正常なコミット」と「壊れているコミット」の間を二分探索してバグの混入コミットを絞り込みます。コミット数が多いほど効果を発揮し、N コミット間の探索を log₂(N) 回のチェックで済ませられます。
# bisect を開始する
git bisect start
# 現在のコミット(HEAD)は壊れている
git bisect bad
# 3 ヶ月前のタグは正常だった
git bisect good v2.0.0
Git が中間のコミットをチェックアウトするので、その状態でアプリを動作確認し、結果を伝えます。
# チェックアウトされたコミットが壊れていれば
git bisect bad
# 正常であれば
git bisect good
これを繰り返すと、Git が最初に問題を導入したコミットを示します。
xxxxxxx is the first bad commit
commit xxxxxxx
Author: ...
# bisect を終了して HEAD を元に戻す
git bisect reset
自動化:bisect run
テストスクリプトがあれば手動確認を省けます。git bisect run に渡すスクリプトは exit 0 で good、1〜127 のうち 125 を除いた値で bad、125 で skip(テスト不能として飛ばす)、**それ以外(128 以上や負の値、シグナルによる終了など)**で abort します。
# ./test.sh が正常終了するかどうかで自動判定
git bisect run ./test.sh
二分探索の流れを図にすると次のとおりです。
flowchart TD
A[git bisect start] --> B[git bisect bad\n現在のコミットはバグあり]
B --> C[git bisect good v2.0.0\n正常なコミットを指定]
C --> D[Git が中間コミットをチェックアウト]
D --> E{バグあり?}
E -- あり --> F[git bisect bad]
E -- なし --> G[git bisect good]
F --> H{範囲が絞れた?}
G --> H
H -- まだ広い --> D
H -- 特定できた --> I[最初のバグ導入コミットを表示]
I --> J[git bisect reset]
git worktree:複数ブランチを同時に開く
状況: feature ブランチで作業中に急ぎのレビューや hotfix 対応が入り、一時的に別ブランチを開きたい。
git worktree を使うと、同じリポジトリに複数の作業ツリー(チェックアウト済みの状態)を持てます。stash に退避してブランチを切り替える必要がなく、別ターミナルで並行作業ができます。
# ../hotfix ディレクトリに hotfix/urgent ブランチをチェックアウト
git worktree add ../hotfix hotfix/urgent
# 現在の worktree 一覧を表示
git worktree list
# /home/user/myproject a3f9c12 [feature/add-search]
# /home/user/hotfix b4d1e23 [hotfix/urgent]
# 不要になった worktree を削除する
git worktree remove ../hotfix
worktree は同じリポジトリを共有しているので、どちらの作業ツリーからでも同じコミット履歴・リモートにアクセスできます。デフォルトでは同じブランチを 2 つの worktree で同時にチェックアウトできません(--force で上書き可能)。
git cherry-pick:特定のコミットだけを取り込む
状況: 別ブランチで修正したバグフィックスを、現在のブランチにも適用したい。
# 特定のコミットを現在のブランチに適用する
git cherry-pick abc1234
# 適用はするがコミットはしない(内容を確認・修正してからコミットしたいとき)
git cherry-pick -n abc1234
# 複数コミットを一括適用(abc123 は含まれず、def456 まで適用)
git cherry-pick abc123..def456
cherry-pick はコミットの「差分」を再適用します。対象コミットと現在のコードが大きく乖離していると、コンフリクトが発生することがあります。その場合は通常のコンフリクト解消手順(編集 → git add → git cherry-pick --continue)で対処します。
コミットの修正:amend と rebase -i
直前のコミットを修正する:—amend
状況: コミット直後に「メッセージが間違っている」「ファイルの追加漏れがあった」と気づいた。
# 追加漏れのファイルをステージしてから
git add src/forgot.ts
# 直前のコミットに含める(エディタでメッセージも編集できる)
git commit --amend
# メッセージだけ変えたい場合
git commit --amend -m "修正したメッセージ"
--amend はコミットを書き換えるため、コミットハッシュが変わります。すでにリモートにプッシュしたコミットを amend すると、チームの全員の履歴とズレが生じます。amend は「まだ push していない」コミットにのみ使いましょう。
複数コミットをまとめて整理する:rebase -i
直近数件のコミットを squash(統合)・reword(メッセージ修正)・drop(削除)したいときは rebase -i を使います。
# 直近3件のコミットを対話的に編集
git rebase -i HEAD~3
エディタが開き、各行の先頭の pick を変えることで操作を指定します。
| キーワード | 動作 |
|---|---|
pick(p) | そのまま使用 |
squash(s) | 直前のコミットに統合(メッセージを結合) |
fixup(f) | 直前のコミットに統合(メッセージは捨てる) |
reword(r) | コミットメッセージを書き直す |
drop(d) | そのコミットを削除する |
詳しい手順は git-bootcamp 第 3 回で解説しています。
取り消し系の整理:reset / revert / restore
Git の「取り消し」は目的によって使うコマンドが変わります。整理すると次のとおりです。
git reset の 3 モード
# HEAD を 1 つ前のコミットに戻す(作業ツリー・インデックスはそのまま)
git reset --soft HEAD~1
# HEAD を戻し、インデックスも戻す(作業ツリーはそのまま)→ デフォルト動作
git reset --mixed HEAD~1
# HEAD・インデックス・作業ツリーをすべて戻す(未コミットの変更は失われる)
git reset --hard HEAD~1
git reset --hard はトラッキング済みファイルの未コミット変更を上書きします。コミット後のツリーは reflog で reset: moving to ... として記録されているため、git reset --hard <reflog-hash> で復元可能です。一方、コミットしていない変更内容(ステージもツリーも未保存のもの)は reflog でも復元できません。実行前に必ず git status で状態を確認してください。
reset / revert / restore の使い分け
| コマンド | 主な対象 | 履歴の変更 | 代表的な用途 |
|---|---|---|---|
git reset [mode] <commit> | コミット(HEAD の位置) | 書き換える(ハッシュが変わる) | ローカルのコミットをなかったことにする |
git revert <commit> | コミット | 残す(取り消しコミットを追加) | 共有ブランチ・push 済みの変更を安全に取り消す |
git restore <pathspec> | ファイル | 変更しない | 作業ツリーやインデックスのファイルを戻す |
push 済みのコミットを取り消したいときは git revert が安全です。新しい「取り消しコミット」が追加されるだけで、既存の履歴は変更されないため、チームの他のメンバーの作業と衝突しません。
# コミット abc1234 の変更を打ち消す新しいコミットを作る
git revert abc1234
その他の便利コマンド
ステージ済みの差分を確認する
# add 済みの変更だけを見る(次のコミットに含まれる内容)
git diff --staged
git diff だけだとステージ前の変更しか見えないので、--staged は git add 後の確認によく使います。
ブランチの全体像を俯瞰する
git log --oneline --graph --all
各ブランチのコミット履歴がグラフで表示され、merge や rebase の形を視覚的に確認できます。エイリアスに登録しておくと便利です。
変更の原因を追う:git blame
# ファイルの各行が「いつ・誰が・どのコミットで」変更したかを表示
git blame src/auth.ts
バグが混入している疑いのあるコードの行を特定したあと、git show <commit> でその変更の全体像を確認するという使い方が典型的です。
.gitignore への後追い追加
# すでに Git がトラッキングしているファイルを追跡対象から外す
git rm --cached .env
.gitignore に追加するだけではすでにトラッキングされているファイルは除外されません。git rm --cached でインデックスから削除することで、以後のコミットでは無視されるようになります(ファイル自体はディスクに残ります)。
出典
- git-stash(1): https://git-scm.com/docs/git-stash
- git-switch(1): https://git-scm.com/docs/git-switch
- git-restore(1): https://git-scm.com/docs/git-restore
- git-reflog(1): https://git-scm.com/docs/git-reflog
- git-bisect(1): https://git-scm.com/docs/git-bisect
- git-worktree(1): https://git-scm.com/docs/git-worktree
- git-cherry-pick(1): https://git-scm.com/docs/git-cherry-pick
- git-reset(1): https://git-scm.com/docs/git-reset
- git-revert(1): https://git-scm.com/docs/git-revert
- git-commit(1)
--amend: https://git-scm.com/docs/git-commit - git-blame(1): https://git-scm.com/docs/git-blame
- git-rm(1): https://git-scm.com/docs/git-rm
- git(1) “Reset, restore and revert”: https://git-scm.com/docs/git