はじめに
Gitサーバを理解しようとして、気づいたら「それっぽいHTTPサーバ」を作っていた話です。
完全なGit互換ではありませんが、Gitの通信の雰囲気を掴む目的ではかなり学びがありました。
作ったもの
Node.jsのhttpモジュールを使って、以下のようなGitっぽいエンドポイントを再現しています:
/info/refs/HEAD/refs/objects/git-upload-pack
実装コード
const http = require("http");
const PORT = 8000;
function send(res, status, headers, body) {
res.writeHead(status, headers);
res.end(body);
}
function gitInfoRefs(service) {
return (
"001e# service=git-upload-pack\n" +
"0000"
);
}
function fakePack() {
return Buffer.from("PACKfakepackfiledata");
}
const server = http.createServer((req, res) => {
const url = new URL(req.url, `http://${req.headers.host}`);
const path = url.pathname;
if (path === "/user/repo.git/info/refs") {
const service = url.searchParams.get("service") || "git-upload-pack";
return send(res, 200, {
"Content-Type": `application/x-${service}-advertisement`,
"Cache-Control": "no-cache",
}, gitInfoRefs(service));
}
if (path === "/user/repo.git/HEAD") {
return send(res, 200, {
"Content-Type": "text/plain",
}, "ref: refs/heads/main\n");
}
if (path.startsWith("/user/repo.git/refs")) {
return send(res, 200, {
"Content-Type": "text/plain",
}, "deadbeefdeadbeefdeadbeef\n");
}
if (path.startsWith("/user/repo.git/objects")) {
return send(res, 200, {
"Content-Type": "application/octet-stream",
}, Buffer.from("fake-object"));
}
if (path === "/user/repo.git/git-upload-pack") {
return send(res, 200, {
"Content-Type": "application/x-git-upload-pack-result",
}, "0008NAK\n" + "0032fake-pack-response\n" + "0000");
}
if (path.includes(".pack")) {
return send(res, 200, {
"Content-Type": "application/octet-stream",
}, fakePack());
}
return send(res, 200, {
"Content-Type": "text/plain",
}, "ok (fake git server)");
});
server.listen(PORT, () => {
console.log(`Fake Git server running on http://localhost:${PORT}`);
});
これで何ができるか
- Gitクライアントっぽいリクエストを受ける
- それっぽいレスポンスを返す
- ただし実際のGitとしては動かない
ハマりポイント
- Gitプロトコルは思ったより複雑
- packfileやオブジェクト管理が完全に別世界
- HTTPだけでは全然足りない
まとめ
- Gitサーバ「っぽいもの」は作れた
- Gitサーバ「そのもの」は別次元
- でも内部構造の理解にはかなり役立つ
おわりに
次はちゃんとしたGitサーバ実装に挑戦したい(たぶん地獄)。