Git は多機能に見えて、中身は単純な部品の組み合わせになっているので、その例を紹介します。
1. ブランチとタグはコミットハッシュのエイリアス
ブランチもタグもないgit
極端な話、ブランチもタグも使わずに、ハッシュだけで Git を運用することは可能です。やりたくないですが。
git checkout e4a1f2c3d5b6a7b8c9d0e1f2a3b4c5d6e7f8a9b0
git merge 1a2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9a0b
エイリアスとしてのブランチとタグ
ブランチは .git/refs/heads/ にある1つのファイルです。
タグも .git/refs/tags/ にあるファイルで、同じ構造になっています。
cat .git/refs/heads/main
# e4a1f2c3d5b6... (40文字のSHA-1)
cat .git/refs/tags/v1.0.0
# a3b2c1d4e5f6... (40文字のSHA-1)
どちらも、40文字のハッシュに人間が読める名前を付ける仕組みに過ぎません。
main と打てば Git が e4a1f2c3... に変換してくれる、ただのエイリアスです。
違いは、ブランチは新規コミットを追いかけてくれることくらいです。
ちなみに origin/main の正体も .git/refs/remotes/origin/main というファイルで、中身はやはり40文字のハッシュです。
ローカルブランチと仕組みは完全に同じで、置き場所が違うだけです。
2. stash はただのコミット
stashの中身
git stash の正体はコミットです。git cat-file -t で型を確認すると commit が返ります。
cat .git/refs/stash
# f7a8b9c0d1e2... (40文字のハッシュ)
git cat-file -t f7a8b9c0d1e2
# commit
中身も普通のコミットと同じ形式で、tree、parent、author、committer、message を持ちます。メッセージは WIP on main: e4a1f2c some message のように自動生成されます。
どのブランチにも属さず、refs/stash というポインタで参照されています。ブランチが refs/heads/ にハッシュを書いて管理しているのと、やっていることは同じです。
push と apply がやっていること
git stash push は、今の変更をコミットとして作り、refs/stash に記録します。
どのブランチにも繋がない独立したコミットを1つ作る操作になります。
git stash applyや pop は、その stash コミットと今の HEAD をマージして変更を復元します。
コミットを取り出して現在の状態に混ぜ込む、というごく普通のマージ操作です。
stashにコミット用の操作をしてみる
stash がただのコミットであるということは、cherry-pick みたいな対コミットの操作がそのままできます。
git cherry-pick stash@{0}
stash の中身を今のブランチにコミットとして取り込むという操作になります。pop と違ってコミットが作られますが、stash がコミットである以上、これは当たり前に動きます。
まとめ
Git の内部はシンプルです。
機能が多く見えるのは、よくある操作をまとめてコマンドとして提供しているからにすぎません。
ぜひ他のコマンドも、内部でどのような操作をしているか考えてみてください。
本ブログに掲載している内容は、私個人の見解であり、所属する組織の立場や戦略、意見を代表するものではありません。