28.

Git の特殊参照 HEAD / ORIG_HEAD / FETCH_HEAD / MERGE_HEAD の意味と使い分け

編集
この記事の要点
  • HEAD = 現在チェックアウトしているコミット(ブランチや detached commit を指すシンボリック参照)
  • ORIG_HEAD = merge / rebase / reset / pull 等の危険操作直前の HEAD を自動退避
  • FETCH_HEAD = 最後の git fetch で取得したコミット(リモート最新)
  • MERGE_HEAD / CHERRY_PICK_HEAD / REVERT_HEAD = それぞれの操作中だけ存在する一時参照
  • やらかしたら git reflog で履歴を確認 → git reset --hard ORIG_HEAD1 つ前の状態に戻せる
  • 参照ファイルは .git/HEAD / .git/ORIG_HEAD 等にプレーンテキストで存在

Git の特殊参照とは

Git では「コミットを指す名前」のことを参照(ref)と呼びます。ブランチ名(main など)やタグ名のほかに、Git が内部的に自動管理する特殊参照がいくつかあり、これらを理解するとreset / merge / rebase失敗からの復旧が格段に楽になります。

参照意味更新タイミング
HEAD現在のコミットcommit / checkout / reset 毎
ORIG_HEAD危険操作前の HEAD バックアップmerge / rebase / reset / pull / am
FETCH_HEAD最後に fetch したコミット群fetch / pull
MERGE_HEADマージしようとしている相手merge 開始時(コンフリクト中のみ)
CHERRY_PICK_HEADcherry-pick 中の対象コミットcherry-pick 中のみ
REVERT_HEADrevert 中の対象コミットrevert 中のみ

HEAD: 現在のコミット

HEAD は「いまどのコミットを見ているか」を表します。通常はブランチ名を指しますが、git checkout 等でdetached HEAD 状態にすると、コミットを直接指すこともあります。

# HEAD の中身を確認
cat .git/HEAD
# → ref: refs/heads/main   (通常はブランチを指す)

cat .git/refs/heads/main
# → e3f4a2b...   (コミットハッシュ)

# HEAD が指すコミットを表示
git rev-parse HEAD
git show HEAD
git log -1 HEAD

# 直前のコミット
git show HEAD~1
git show HEAD^

# 2 つ前
git show HEAD~2
git show HEAD^^

# HEAD の親が複数ある(マージコミット)場合
git show HEAD^1    # 第 1 親
git show HEAD^2    # 第 2 親

ORIG_HEAD: 危険操作前のバックアップ

Git は merge / rebase / reset / pull / am など履歴を書き換える可能性のある操作の直前に、その時点の HEAD を ORIG_HEAD として保存します。「やらかしたらまず ORIG_HEAD」と覚えておくと救われます。

# やらかしパターン1: reset --hard で消した
git log --oneline
# a1b2c3d (HEAD) ← いまここ
# e4f5g6h ← 大事なコミット
git reset --hard HEAD~3   # 3 つ消してしまった!

# → ORIG_HEAD には reset 直前の a1b2c3d が残っている
git show ORIG_HEAD

# 元に戻す
git reset --hard ORIG_HEAD

# やらかしパターン2: マージで予想外の変更が入った
git merge feature-x
# 大量にコンフリクト&意図しない変更
git merge --abort                # 進行中ならこれ
# 完了済なら
git reset --hard ORIG_HEAD       # マージ前に戻る

# やらかしパターン3: rebase が大荒れ
git rebase main
# 履歴がぐちゃぐちゃに
git rebase --abort               # 進行中なら
git reset --hard ORIG_HEAD       # 完了済なら

FETCH_HEAD: 最後の fetch 結果

git fetch でリモートから取得したコミット群が FETCH_HEAD に記録されます。git pull は内部的に「fetchFETCH_HEAD を merge」を行っています。

# fetch だけ実行(merge はしない)
git fetch origin

cat .git/FETCH_HEAD
# 8c1d... not-for-merge   branch 'develop' of github.com:...
# a2f9... branch 'main'    of github.com:...
# → "not-for-merge" は明示的に取得していないブランチ

# 取得した最新を差分確認
git log HEAD..FETCH_HEAD --oneline
git diff HEAD FETCH_HEAD

# 手動でマージ
git merge FETCH_HEAD

# 特定リモートだけ FETCH_HEAD を見たい
git fetch origin main
git show FETCH_HEAD

MERGE_HEAD: マージ中の相手

マージがコンフリクトで止まっている間のみ存在します。コンフリクト解消の指針になります。

git merge feature-x
# CONFLICT! ... が表示
# → .git/MERGE_HEAD に feature-x の先頭コミットが書かれる

cat .git/MERGE_HEAD
# 9f8e7d6c5b4a...

# コンフリクト中に「相手側の変更」を確認
git show MERGE_HEAD:src/Login.php

# 「自分側」と「相手側」を見比べる
git show HEAD:src/Login.php          # 自分(現在ブランチ)
git show MERGE_HEAD:src/Login.php    # 相手

# マージを中断(HEAD と MERGE_HEAD 関連を全部リセット)
git merge --abort

reflog: 全ての HEAD 移動の履歴

ORIG_HEAD だけでは「2 操作前」までしか戻せませんが、git reflog なら過去 90 日分の HEAD 移動を全て確認できます。

# 過去の HEAD 移動を一覧
git reflog
# a1b2c3d HEAD@{0}: reset: moving to HEAD~3
# e4f5g6h HEAD@{1}: commit: 大事な変更
# 1234567 HEAD@{2}: commit: その前

# 特定の地点に戻る
git reset --hard HEAD@{1}
git reset --hard e4f5g6h    # ハッシュ指定でも可

# 指定ブランチの reflog
git reflog show main

# 全 ref の reflog
git reflog show --all

# 期限切れ前に手動 gc 防止(90 日デフォルト)
git config gc.reflogExpire 200.days

参照を指定する記法(rev 表記)

記法意味
HEAD現在
HEAD^ / HEAD~11 つ前
HEAD~33 つ前
HEAD^2マージコミットの第 2 親
HEAD@{2}reflog の 2 つ前
HEAD@{yesterday}昨日時点の HEAD
HEAD@{2.hours.ago}2 時間前の HEAD
main..HEADmain から HEAD までの差分
main...HEAD共通祖先からの両方向差分

FAQ

Q: ORIG_HEAD はいつ消える?
A: 次の危険操作(merge / reset / rebase 等)が走るまで保持されます。pull を立て続けにやると上書きされるので注意。確実に保存したい場合は別ブランチ git branch backup ORIG_HEAD を作ってから操作。

Q: detached HEAD 状態でコミットしたら?
A: ブランチに紐付かないコミットになり、checkout で離れると参照されなくなります。git reflog で見つけて git branch save で救出。

Q: reflog にも無いコミットを復元したい
A: git fsck --lost-found.git/lost-found/ に dangling commit が出ます。gc されていなければ救えます。

編集
Post Share
子ページ

子ページはありません

同階層のページ
  1. 用語一覧
  2. エラー一覧
  3. git本体のインストール(Linux)
  4. Linuxサーバーへのgit導入とクライアントのセットアップ
  5. リモートリポジトリをローカルリポジトリとしてクローンする方法
  6. リモートとローカルのリポジトリを同期(pull)する方法
  7. 設定の確認
  8. gitユーザー名とemailの設定
  9. リモートリポジトリの作成
  10. ローカルリポジトリの作成
  11. 新規ファイル/ディレクトリをインデックスに登録
  12. インデックスの登録状態を確認
  13. ローカルリポジトリの変更をコミット
  14. コミット履歴の確認
  15. クライアントからリモートリポジトリの接続設定、確認、削除
  16. リポジトリへのプッシュ
  17. リモートリポジトリからクライアントへのSSHクローン
  18. リモートとローカルの差分表示
  19. バージョンの確認
  20. プロキシの設定
  21. ローカルをリモートリポジトリの状態に戻す
  22. ブランチの作成, 一覧表示, 切り替え
  23. ブランチのマージと削除
  24. リベース
  25. .gitignoreの書き方
  26. .gitignoreの設定が反映されない場合
  27. 特定のファイルをgitの管理から外す方法
  28. 参照(ORIG_HEAD, HEAD, FETCH_HEAD)
  29. git rm [-r --cached] の取り消し
  30. 一部のディレクトリ/ファイルのみをリポジトリから復元する方法
  31. ローカルとリモートリポジトリの有無を同期
  32. pushの取消し方法
  33. マージツールの起動方法
  34. Gitで「MERGING」の状態a