Git Plumbing: ‘commit’や’push’だけで終わらせない、Gitの内部構造を「解剖」する方法

Git tutorial - IT technology blog
Git tutorial - IT technology blog

Gitは単なるcommitやpushコマンドの集まりではない

本番環境で誤って git reset --hard を実行してしまい、冷や汗をかいたことはありませんか?キャリアを始めたばかりの頃、私はGitを単なる高機能なファイルのコピペツールだと思っていました。しかし、大規模なリポジトリの構造を壊してしまった時、通常のコマンドが通用しないことを痛感しました。その時、私は .git ディレクトリの奥深くまで潜り込み、データの断片を一つずつ拾い集めることを余儀なくされたのです。

多くの開発者は git statusgit branchgit checkout といったコマンドにしか馴染みがありません。Gitの世界では、これらは Porcelain(磁器)コマンドと呼ばれます。これらは洗面台のようなものです。見た目が美しく、使いやすく、清潔です。しかし、そのシステムを機能させるためには、床下に張り巡らされた複雑な配管網が必要です。それが Plumbing(配管)コマンドです。

Plumbingコマンドをマスターすれば、Gitでエラーが発生した際の「厄介な」状況に対処できるようになります。さらに、チーム独自の開発支援ツールを自作するための基礎にもなります。

Gitの実体はコンテンツ指向のストレージシステム(Key-Value)

多くの人が、Gitはバージョン間の差分(diff)を保存していると誤解しています。実際には、GitはシンプルなKey-Valueシステムとして動作します。Keyはファイル内容から生成される40文字のSHA-1ハッシュ値であり、Valueはそのデータそのものです。

例えば、1GBのファイルの中でカンマを1つだけ変更したとします。Gitはそのカンマだけを保存するのではなく、全く新しいオブジェクトを作成します。zlib圧縮メカニズムのおかげで、リポジトリの容量は最適化されたまま保たれます。これらのオブジェクトを直接操作するには、hash-objectcat-filerev-parse の3つのコマンドが必要です。

Git Plumbing of operations実践

1. Git hash-object: データに識別子を付与する

このコマンドはデータを受け取り、SHA-1ハッシュを計算して、必要に応じてGitのデータベースに直接保存します。これは、私たちが毎日 git add を行う際に裏側で実行されている処理そのものです。

# 保存せずにハッシュ値のみを計算
echo "itfromzero tutorial" | git hash-object --stdin

# 計算して.git/objectsに直接保存
echo "hello world" | git hash-object -w --stdin

-w(write)フラグを付けると、Gitは .git/objects 内にファイルを作成します。ハッシュ値が 3b18e512... の場合、ファイルは .git/objects/3b/18e512... に配置されます。最初の2文字をディレクトリ名に分けることで、一つのフォルダに数万個 hosting のファイルが集中してシステムが低速化するのを防いでいます。

コマンドを打たずにファイルのチェックサムを素早く確認したい時は、よく Hash Generator を使います。このツールは完全にブラウザ上で動作するため、機密データでも非常に安全です。

2. Git cat-file: オブジェクトを覗く顕微鏡

ハッシュ値がわかったところで、中身はどうやって読むのでしょうか? git cat-file は、blob、tree、commitなどの各種オブジェクトをデコードするためのツールです。

# オブジェクトのタイプを確認
git cat-file -t 3b18e512dba79e4c8300dd08aeb37f8e728b8dad

# 展開された内容を表示
git cat-file -p 3b18e512dba79e4c8300dd08aeb37f8e728b8dad

-p(pretty print)フラグは、壊れたコミットをデバッグする際の救世主です。特定の時点において、そのコミットがどのファイルを指しているのかを追跡するのに役立ちます。

3. Git rev-parse: コンピュータのための通訳者

人間は mainHEADv1.0 といった覚えやすい名前を好みます。対照的に、Gitは無機質なSHA-1ハッシュ値しか理解しません。git rev-parse は、両者の間の通訳者の役割を果たします。

# 現在のブランチのハッシュ値を取得
git rev-parse main

# HEADの1つ前のコミットのハッシュ値を取得
git rev-parse HEAD~1

CI/CDスクリプトでは、よく git rev-parse HEAD を使用します。これにより、ブランチ名に関係なく、デプロイされるバージョンの正確なIDを取得してログに記録できます。

なぜこれらの「地味な」コマンドを気にする必要があるのか?

GitKrakenやVS CodeのようなGUI は非常に便利ですが、問題の根本的な解決には役立たないことがあります。Plumbingを理解することには、主に3つのメリットがあります。

  • データ復旧: 私は以前、dangling blobs(どのブランチからも参照されていないデータの断片)をスキャンすることで、誤って削除したコードの90%を復元したことがあります。
  • 容量の最適化: cat-file とスクリプトを組み合わせることで、リポジトリのクローン速度を低下させている「重い」ファイルを見つけ出すことができます。
  • 仕組みの習得: GitがKey-Valueファイルの集まりであることを理解すれば、「detached HEAD」のような概念に戸惑うことはなくなります。

ログの処理やGitフックの設定を行う際、私はよく JSON Formatter を使ってデータを整理します。優れたツールとシステム的な思考を組み合わせることで、作業効率は劇的に向上します。

結びに代えて

Plumbingコマンドを学ぶことは、車のボンネットを開けてエンジンがどのように動いているかを確認するようなものです。毎日エンジンを修理することはないかもしれませんが、その知識は道端で車が故障した時にあなたを救ってくれるでしょう。空のリポジトリを作成し、低レイヤーのコマンドを使って手動でコミットを構築してみてください。Gitが実は非常にロジカルで透明性が高く、決して謎めいた「ブラックボックス」ではないことがわかるはずです。

Share: