こんにちは。Gitの世界へようこそ。
普段、私たちは `git commit` や `git push` といった「Porcelain(磁器)」コマンドを使ってGitを操作しています。これらは非常に便利ですが、裏を返せば、Gitが内部でどうやってデータを保持しているのかという「本質」からは少し遠い場所にいるということです。
もし、歴史改変が必要な場面(例えば、誤ってコミットした巨大なバイナリの完全消去や、複雑に絡み合ったリポジトリの歴史の再構築)に直面したとき、`git filter-repo` のような高機能ツールですら「魔法」に見えてしまうかもしれません。
今回は、その「魔法」の正体を暴きます。Gitの心臓部である「Plumbing(配管)」コマンドを使い、コミットグラフを直接書き換える。この領域に踏み込むと、あなたはGitのマスターになります。
—
1. Gitの心臓部:Plumbingコマンドとは何か?
Gitは実は非常にシンプルな「キー・バリュー・ストア」です。すべてのデータは以下の3つのオブジェクトで管理されています。
- Blob: ファイルの中身そのもの。
- Tree: ディレクトリ構造。Blobや他のTreeへの参照を持つ。
- Commit: Treeへのポインタ、親コミットへのポインタ、メタデータ。
これらを直接操作する「配管(Plumbing)」コマンドは、外科手術のような正確さを提供します。
2. 環境準備:実験場の作成
まずは、安全に破壊的な操作を行うための実験場を作りましょう。
実験用リポジトリの作成
mkdir git-lab && cd git-lab
git init
echo “Hello, Git-Plumbing!” > hello.txt
git add hello.txt
git commit -m “Initial commit”
3. 基礎セットアップ:オブジェクトを覗き見る
まず、現在のコミットがどうなっているか見てみましょう。
現在のHEADのハッシュを確認
git rev-parse HEAD
cat-fileでオブジェクトの中身を覗く (-pはpretty print)
git cat-file -p HEAD
ここで出力される `tree xxxx…` というのが、このコミットが指しているディレクトリ構造です。さらにその中身も見られます。
treeの中身を表示 — ここからが本題です。既存のコミットを無視して、手動で新しいコミットを作成してみます。 `hash-object` を使うと、ファイルをGitのデータベースに登録し、そのハッシュ値を得ることができます。 新しいファイルを作成してデータベースへ登録 `mktree` を使って、ファイル構造を定義します。 権限 100644 (通常ファイル), blobハッシュ, ファイル名 `commit-tree` を使います。ここで親コミットを指定することで、グラフが繋がります。 親を指定してコミットを作成 最後に、作成したコミットを現在のブランチに反映させます。 git update-ref refs/heads/main $COMMIT_HASH — 「なぜわざわざ面倒なことを?」と思うかもしれません。しかし、この手法を知ると以下のことが可能になります。 1. 歴史の完全改竄: コミットの中身を微調整し、署名を保持したままグラフを繋ぎ変えることができます。 この操作は、「Gitの仕組み」という地図を持つ者だけが許される特権です。操作を誤れば歴史が消滅しますが、`git reflog` がある限り、Gitは必ずあなたを救い出してくれます。 まずは、自分の捨ててもいいリポジトリで、今回紹介したコマンドを組み合わせて「コミットを捏造する」遊びから始めてみてください。それができれば、あなたはもうGitを「使う人」ではなく「操る人」です。 次は、これらを組み合わせて「コミットのタイムスタンプだけを書き換える」スクリプトに挑戦してみるのも面白いですよ。頑張ってくださいね!
git cat-file -p 4. HelloWorld:手動でコミットを作る
ステップ1:Blobを作成(ファイルを登録)
echo “New content” > feature.txt
BLOB_HASH=$(git hash-object -w feature.txt)
echo “Blob created: $BLOB_HASH”ステップ2:Treeを作成
echo “100644 blob $BLOB_HASH feature.txt” | git mktree
出力されたツリーハッシュを保存
TREE_HASH=$(echo “100644 blob $BLOB_HASH feature.txt” | git mktree)ステップ3:コミットを作成
COMMIT_HASH=$(echo “Manual commit from plumbing” | git commit-tree $TREE_HASH -p HEAD)
echo “New commit created: $COMMIT_HASH”ステップ4:HEADを付け替える
git reset –hard5. なぜこのテクニックが最強なのか
2. リポジトリの最適化: 肥大化したオブジェクトを手動で特定し、`filter-repo` でも消せないような深い傷跡を修正できます。
3. 自動化の極致: 複雑なマージアルゴリズムを自作し、CI上で動的なコミット生成を行うことができます。先輩エンジニアからのアドバイス