pkg/testutil/bldr/git_smart_http_server.go (95 lines of code) (raw):
package bldr
import (
"fmt"
"net"
"net/http"
"testing"
"github.com/go-git/go-git/v5"
"github.com/go-git/go-git/v5/plumbing/format/pktline"
"github.com/go-git/go-git/v5/plumbing/protocol/packp"
"github.com/go-git/go-git/v5/plumbing/transport"
"github.com/go-git/go-git/v5/plumbing/transport/server"
)
// StartGitSmartHTTPServer starts a Git Smart HTTP Server.
// Pushing to the Git Server is not supported.
// Shallow clones are not supported.
// Not thread-safe.
func StartGitSmartHTTPServer(t *testing.T, repo *git.Repository) string {
listener, port := TCPPort(t).Listen("0")
gitServer := NewGitSmartHTTPServer(listener, repo)
gitServer.Serve()
t.Cleanup(gitServer.Close)
return fmt.Sprintf("http://127.0.0.1:%s/", port)
}
type GitSmartHTTPServer struct {
repo *git.Repository
listener net.Listener
httpServer *http.Server
}
func NewGitSmartHTTPServer(listener net.Listener, repo *git.Repository) *GitSmartHTTPServer {
return &GitSmartHTTPServer{
repo: repo,
listener: listener,
httpServer: nil,
}
}
func (s *GitSmartHTTPServer) Serve() {
// See https://git-scm.com/docs/http-protocol/2.34.0#_smart_clients
mux := http.NewServeMux()
mux.HandleFunc("/info/refs", s.handleAdvertizedRefs)
mux.HandleFunc("/git-upload-pack", s.handleUploadPack)
s.httpServer = &http.Server{Handler: mux}
go func() { _ = s.httpServer.Serve(s.listener) }()
}
func (s *GitSmartHTTPServer) Close() {
if s.httpServer != nil {
_ = s.httpServer.Close()
}
}
func (s *GitSmartHTTPServer) handleAdvertizedRefs(w http.ResponseWriter, r *http.Request) {
if r.URL.Query().Get("service") != "git-upload-pack" {
http.Error(w, `"Dumb" HTTP Git clients are not supported`, http.StatusNotImplemented)
return
}
session, err := s.establishUploadPackSession()
if err != nil {
http.Error(w, fmt.Sprintf("Failed to establish git-upload-pack session: %v", err), http.StatusInternalServerError)
return
}
advRefs, err := session.AdvertisedReferencesContext(r.Context())
if err != nil {
http.Error(w, fmt.Sprintf("Failed to retrieve the advertised references: %v", err), http.StatusInternalServerError)
return
}
w.Header().Add("Content-Type", "application/x-git-upload-pack-advertisement")
w.Header().Add("Cache-Control", "no-cache")
advRefs.Prefix = append(advRefs.Prefix, []byte("# service=git-upload-pack"), pktline.Flush)
err = advRefs.Encode(w)
if err != nil {
panic(err) // too late to write the error to the response
}
}
func (s *GitSmartHTTPServer) handleUploadPack(w http.ResponseWriter, r *http.Request) {
uploadReq := packp.NewUploadPackRequest()
err := uploadReq.Decode(r.Body)
if err != nil {
http.Error(w, fmt.Sprintf("Failed to decode HTTP request body: %v", err), http.StatusBadRequest)
return
}
session, err := s.establishUploadPackSession()
if err != nil {
http.Error(w, fmt.Sprintf("Failed to establish git-upload-pack session: %v", err), http.StatusInternalServerError)
return
}
uploadResponse, err := session.UploadPack(r.Context(), uploadReq)
if err != nil {
http.Error(w, fmt.Sprintf("Failed to upload pack: %v", err), http.StatusInternalServerError)
return
}
w.Header().Add("Content-Type", "application/x-git-upload-pack-result")
w.Header().Add("Cache-Control", "no-cache")
err = uploadResponse.Encode(w)
if err != nil {
panic(err) // too late to write the error to the response
}
}
func (s *GitSmartHTTPServer) establishUploadPackSession() (transport.UploadPackSession, error) {
endpoint, _ := transport.NewEndpoint("/")
gitServer := server.NewServer(server.MapLoader{endpoint.String(): s.repo.Storer})
return gitServer.NewUploadPackSession(endpoint, nil)
}