JVM系言語でGitサーバを作ろうとしたら、JGitのGitServerを使うと簡単ですが、Clojureの場合ringで動かしたいので、Servletだとちょっと扱いづらいところがあります。
そこで、GitServletを使わずにGit http サーバを作ってみます。
Gitの内側 - トランスファープロトコル にあるとおりのプロトコルを実装すればよいわけですが
、JGitを使えば簡単です。
例えば、git cloneを実行した時には、サーバへは以下の2回のHTTPリクエストがとびます。
GET /hoge.git/info/refs?service=git-upload-pack
POST /hoge.git/git-upload-pack
最初の info/refs はブランチの一覧を返します。
昔のgitクライアントからはservice=git-upload-packのパラメータが付いてきません。これはdumbクライアントと呼ばれています。現在のgitクライアントはsmartクライアントと呼ばれていて、dumbクライアントがリポジトリ下のinfo/refsファイルの内容をそのまま送信するのに対し、多少のヘッダ情報がついたりします。
そんな調子で、gitコマンドでやりとりされるURLを実装します。基本的にはJGitのメソッドを呼ぶだけです。
(defroutes git-routes
(compojure/context ["/:name.git", :name #"[0-9A-Za-z\-\.]+"] {{repo-name :name} :params}
(GET "/info/refs" {:as request}
(get-info-refs (open-repo repo-name) request))
(POST "/git-upload-pack" {:as request}
(service-upload (open-repo repo-name) request))
(POST "/git-receive-pack" {:as request}
(service-receive (open-repo repo-name) request))
(GET "/HEAD" {params :params}
(get-text-file (open-repo repo-name) "HEAD"))
(GET "/objects/info/alternates" []
(get-text-file (open-repo repo-name) "objects/info/alternates"))
(GET "/objects/info/http-alternates" []
(get-text-file (open-repo repo-name) "objects/info/http-alternates"))
(GET "/objects/info/packs" []
(get-info-packs (open-repo repo-name)))
(GET "/objects/info/*" {params :params}
(get-text-file (open-repo repo-name) (str "objects/info/" (params :*))))
(GET ["/objects/:hash2/:hash38", :hash2 #"[0-9a-f]{2}" :hash38 #"[0-9a-f]{38}"] [hash2 hash38]
(get-loose-object (open-repo repo-name) (str "objects/" hash2 hash38)))
(GET ["/objects/pack/:pack-file", :pack-file #"[0-9a-f]\.(pack|idx)"] [pack-file]
(cond
(. pack-file endsWith ".pack")
(get-pack-file (open-repo repo-name) (str "objects/pack/" pack-file))
(. pack-file endsWith ".idx")
(get-idx-file (open-repo repo-name) (str "objects/pack/" pack-file))))))
僅か171行で、GitServerが完成しました。
https://github.com/kawasima/gring/blob/master/src/gring/core.clj
簡単ですね。