🔍
detached HEADを意図的に再現して検証:fast-forward
毎日gitは使うのですが、detached HEAD など普段あまり使わない操作をした際の備忘録メモとして、そしてSourceTree上とTerminal上の表時に違いがあるのか?など一度じっくり比較/観察したかったので検証しました。
環境
pc:MacBook Pro(2019)
os:macos Sequoia
Git:2.49.0
SourceTree:内蔵 Git 2.46.0
検証目的
- HEADの仕組みと挙動を理解するために、意図的に detached HEAD状態 を作成
- TerminalとSourceTreeでの表示の違いを観察
- 実務で使う機会は少ないが、Git内部の動き理解のために有益
- 「意図的にdetached HEAD」「git push origin HEAD:main」は実務では稀だと思います(今回はdetached HEAD を検証するため)
- fast-forward がどのように発生し、どのように統合されるかを段階的に検証
検証内容
- main ブランチでローカルコミット → push
- HEAD~0 に checkout → detached HEAD状態へ移行
- 分離状態で変更 → commit → git push origin HEAD:main
- main に戻ってもローカルの HEAD はリモート(origin/main)より1コミット遅れているが、履歴は直線的で分岐していない
- main は origin/main より1コミット遅れているが、履歴が直線的に繋がっているため、git merge origin/main を実行すると fast-forward マージ が行われる
🔗 関連記事:「detached HEADを意図的に再現して検証:non-fast-forward」
fast-forward
- fast-forward(早送り)マージとは、現在のブランチが遅れており、かつ分岐がない直線的な履歴である場合に、単純に先に進める形で統合できる状態
- 今回のように、detached HEAD で更新した変更を先に push し、その後 main に戻ると、main は origin/main より1コミット遅れている状態になる。 ただし履歴は分岐していないため、git merge origin/main を実行することで fast-forward マージが行われ、ポインタを進めるだけで履歴が直線的に統合される。
detached HEADで更新 → push → mainブランチでfast-forwardマージ
(main)ファイル作成 + add + commit
$ echo "local" > file.txt$ git add .$ git commit -m "first commit"
- リモートにはまだpushしていないため HEAD → main が先に進んでいる状態
HEAD → main
origin/HEAD, origin/main


(main)push
$ git push
- リモートpush済。HEAD → main、 origin/HEAD, origin/mainが同じコミットIDを参照している状態(同じ位置)
HEAD → main、origin/HEAD, origin/main


detached HEAD + HEADでファイル更新 + add + commit
$ git checkout HEAD~0 # 現在の HEAD が指しているコミットに そのままチェックアウト$ echo "local(HEAD) update" >> file.txt$ git add .$ git commit -m "HEAD(detach)to file update"
- HEADとmainが分離した事が確認できる
- (Terminal上での表示)HEAD → main ではなくHEAD と main に別れている
- (SourceTree)レフトメニュー内の「ブランチ」でmainとHEADが表示され、HEADにチェックアウトしている
- HEAD、main、origin/HEAD, origin/mainが同じコミットIDを参照している状態(位置は変わらず同じ位置)
HEAD、main、origin/HEAD, origin/main


(HEADにcheckoutしている状態)origin/mainにpush
# HEADをorigin/mainにpush
$ git push origin HEAD:main
- HEAD, origin/main, origin/HEADが先に進み、mainだけが遅れている状態
- (Terminal上での表示)HEADをorigin/mainにpushしたのでorigin/HEADもorigin/mainに追従して同じ位置
- (SourceTree)mainで[1コミット遅れ]になっている(mainだけが遅れている状態)
HEAD、origin/HEAD, origin/main
main


mainにチェックアウト
$ git checkout main
- origin/main, origin/HEADが先行、HEAD → mainが遅れている状態
- main にcheckoutしたので再び HEAD → main となる
- HEAD → main だが main はリモート先行分を取り込んでいないため1コミット遅れ
origin/HEAD, origin/main
HEAD → main


(main)fast-forward merge
- リモートが1コミット先に進んでいるだけなので、git merge origin/main により fast-forward merge(マージコミットなし、ポインタを進めるのみ) で統合される
[fast-forward merge実行前]
c8ff231 ← Initial commit
↓
2e279ce ← HEAD → main(ローカル)
↓
ef02fbe ← origin/main, origin/HEAD
↓↓↓↓↓
[fast-forward merge実行後]
c8ff231
↓
2e279ce
↓
ef02fbe ← HEAD → main, origin/main, origin/HEAD


Discussion