all repos

mugit @ 91e70b7

馃惍 git server that your cow will love
2 files changed, 61 insertions(+), 35 deletions(-)
git: refactor how git http's done
Author: Oleksandr Smirnov olexsmir@gmail.com
Committed at: 2026-03-02 14:58:03 +0200
Authored at: 2026-03-02 14:16:26 +0200
Change ID: vmzkklkkqyzluwzotxvolmtlmmlswvnk
Parent: 5957dc0
M internal/handlers/git.go
路路路
        1
        1
         package handlers

      
        2
        2
         

      
        3
        3
         import (

      
        
        4
        +	"compress/gzip"

      
        4
        5
         	"fmt"

      
        5
        6
         	"io"

      
        6
        7
         	"log/slog"

      路路路
        10
        11
         	"olexsmir.xyz/mugit/internal/git/gitx"

      
        11
        12
         )

      
        12
        13
         

      
        13
        
        -// multiplex, check if the request smells like gitprotocol-http(5), if so, it

      
        14
        
        -// passes it to git smart http, otherwise renders templates

      
        15
        
        -func (h *handlers) multiplex(w http.ResponseWriter, r *http.Request) {

      
        16
        
        -	if r.URL.RawQuery == "service=git-receive-pack" {

      
        17
        
        -		w.WriteHeader(http.StatusBadRequest)

      
        18
        
        -		w.Write([]byte("http pushing is not supported"))

      
        
        14
        +func (h *handlers) infoRefsHandler(w http.ResponseWriter, r *http.Request) {

      
        
        15
        +	path, err := h.checkRepoPublicityAndGetPath(r.PathValue("name"), "")

      
        
        16
        +	if err != nil {

      
        
        17
        +		h.gitError(w, http.StatusNotFound, "repository not found")

      
        19
        18
         		return

      
        20
        19
         	}

      
        21
        20
         

      
        22
        
        -	path := r.PathValue("rest")

      
        23
        
        -	if path == "info/refs" && r.Method == "GET" && r.URL.RawQuery == "service=git-upload-pack" {

      
        24
        
        -		h.infoRefs(w, r)

      
        25
        
        -	} else if path == "git-upload-pack" && r.Method == "POST" {

      
        26
        
        -		h.uploadPack(w, r)

      
        27
        
        -	} else if r.Method == "GET" && path == "" {

      
        28
        
        -		h.repoIndex(w, r)

      
        29
        
        -	} else {

      
        30
        
        -		h.write404(w, nil)

      
        
        21
        +	service := r.URL.Query().Get("service")

      
        
        22
        +	switch service {

      
        
        23
        +	case "git-upload-pack":

      
        
        24
        +		w.Header().Set("Content-Type", "application/x-git-upload-pack-advertisement")

      
        
        25
        +		w.Header().Set("Connection", "Keep-Alive")

      
        
        26
        +		w.Header().Set("Cache-Control", "no-cache, max-age=0, must-revalidate")

      
        
        27
        +

      
        
        28
        +		w.WriteHeader(http.StatusOK)

      
        
        29
        +		if err := gitx.InfoRefs(r.Context(), path, w); err != nil {

      
        
        30
        +			h.gitError(w, http.StatusInternalServerError, err.Error())

      
        
        31
        +			slog.Error("git: info/refs", "err", err)

      
        
        32
        +			return

      
        
        33
        +		}

      
        
        34
        +

      
        
        35
        +	case "git-receive-pack":

      
        
        36
        +		h.receivePackHandler(w, r)

      
        
        37
        +

      
        
        38
        +	default:

      
        
        39
        +		h.gitError(w, http.StatusBadRequest, "service unsupported")

      
        31
        40
         	}

      
        32
        41
         }

      
        33
        42
         

      
        34
        
        -func (h *handlers) infoRefs(w http.ResponseWriter, r *http.Request) {

      
        
        43
        +const uploadPackExpectedContentType = "application/x-git-upload-pack-request"

      
        
        44
        +

      
        
        45
        +func (h *handlers) uploadPackHandler(w http.ResponseWriter, r *http.Request) {

      
        35
        46
         	path, err := h.checkRepoPublicityAndGetPath(r.PathValue("name"), "")

      
        36
        47
         	if err != nil {

      
        37
        
        -		h.write404(w, err)

      
        
        48
        +		h.gitError(w, http.StatusNotFound, "repository not found")

      
        38
        49
         		return

      
        39
        50
         	}

      
        40
        51
         

      
        41
        
        -	w.Header().Set("content-type", "application/x-git-upload-pack-advertisement")

      
        42
        
        -	w.WriteHeader(http.StatusOK)

      
        43
        
        -	if err := gitx.InfoRefs(r.Context(), path, w); err != nil {

      
        44
        
        -		w.WriteHeader(http.StatusInternalServerError)

      
        45
        
        -		slog.Error("git: info/refs", "err", err)

      
        
        52
        +	contentType := r.Header.Get("Content-Type")

      
        
        53
        +	if contentType != uploadPackExpectedContentType {

      
        
        54
        +		h.gitError(w, http.StatusUnsupportedMediaType, "provided content type is not supported")

      
        46
        55
         		return

      
        47
        56
         	}

      
        48
        
        -}

      
        49
        57
         

      
        50
        
        -func (h *handlers) uploadPack(w http.ResponseWriter, r *http.Request) {

      
        51
        
        -	path, err := h.checkRepoPublicityAndGetPath(r.PathValue("name"), "")

      
        52
        
        -	if err != nil {

      
        53
        
        -		h.write404(w, err)

      
        54
        
        -		return

      
        
        58
        +	bodyReader := r.Body

      
        
        59
        +	if r.Header.Get("Content-Encoding") == "gzip" {

      
        
        60
        +		gzipReader, err := gzip.NewReader(r.Body)

      
        
        61
        +		if err != nil {

      
        
        62
        +			h.gitError(w, http.StatusInternalServerError, err.Error())

      
        
        63
        +			slog.Error("git: failed to create gzip reader", "err", err)

      
        
        64
        +			return

      
        
        65
        +		}

      
        
        66
        +		defer gzipReader.Close()

      
        
        67
        +		bodyReader = gzipReader

      
        55
        68
         	}

      
        56
        69
         

      
        57
        70
         	w.Header().Set("Content-Type", "application/x-git-upload-pack-result")

      
        58
        
        -	w.Header().Set("Cache-Control", "no-cache")

      
        
        71
        +	w.Header().Set("Connection", "Keep-Alive")

      
        
        72
        +	w.Header().Set("Cache-Control", "no-cache, max-age=0, must-revalidate")

      
        
        73
        +

      
        59
        74
         	w.WriteHeader(http.StatusOK)

      
        60
        
        -

      
        61
        
        -	if err := gitx.UploadPack(r.Context(), path, true, r.Body, newFlushWriter(w)); err != nil {

      
        
        75
        +	if err := gitx.UploadPack(r.Context(), path, true, bodyReader, newFlushWriter(w)); err != nil {

      
        
        76
        +		h.gitError(w, http.StatusInternalServerError, err.Error())

      
        62
        77
         		slog.Error("git: upload-pack", "err", err)

      
        63
        78
         		return

      
        
        79
        +

      
        64
        80
         	}

      
        
        81
        +}

      
        
        82
        +

      
        
        83
        +func (h *handlers) receivePackHandler(w http.ResponseWriter, _ *http.Request) {

      
        
        84
        +	h.gitError(w, http.StatusForbidden, "pushes are only supported over ssh")

      
        65
        85
         }

      
        66
        86
         

      
        67
        87
         func (h *handlers) archiveHandler(w http.ResponseWriter, r *http.Request) {

      路路路
        83
        103
         		slog.Error("git: archive", "ref", ref, "err", err)

      
        84
        104
         		return

      
        85
        105
         	}

      
        
        106
        +}

      
        
        107
        +

      
        
        108
        +func (h *handlers) gitError(w http.ResponseWriter, code int, msg string) {

      
        
        109
        +	w.Header().Set("content-type", "text/plain; charset=UTF-8")

      
        
        110
        +	w.WriteHeader(code)

      
        
        111
        +	fmt.Fprintf(w, "%s\n", msg)

      
        86
        112
         }

      
        87
        113
         

      
        88
        114
         func (h *handlers) checkRepoPublicityAndGetPath(name string, ref string) (string, error) {

      
M internal/handlers/handlers.go
路路路
        39
        39
         	mux.HandleFunc("GET /", h.indexHandler)

      
        40
        40
         	mux.HandleFunc("GET /index.xml", h.indexFeedHandler)

      
        41
        41
         	mux.HandleFunc("GET /static/{file}", h.serveStaticHandler)

      
        42
        
        -	mux.HandleFunc("GET /{name}", h.multiplex)

      
        43
        
        -	mux.HandleFunc("POST /{name}", h.multiplex)

      
        44
        
        -	mux.HandleFunc("GET /{name}/{rest...}", h.multiplex)

      
        45
        
        -	mux.HandleFunc("POST /{name}/{rest...}", h.multiplex)

      
        
        42
        +	mux.HandleFunc("GET /{name}", h.repoIndex)

      
        
        43
        +	mux.HandleFunc("GET /{name}/info/refs", h.infoRefsHandler)

      
        
        44
        +	mux.HandleFunc("POST /{name}/git-upload-pack", h.uploadPackHandler)

      
        
        45
        +	mux.HandleFunc("POST /{name}/git-receive-pack", h.receivePackHandler)

      
        46
        46
         	mux.HandleFunc("GET /{name}/feed/{$}", h.repoFeedHandler)

      
        47
        47
         	mux.HandleFunc("GET /{name}/tree/{ref}/{rest...}", h.repoTreeHandler)

      
        48
        48
         	mux.HandleFunc("GET /{name}/blob/{ref}/{rest...}", h.fileContentsHandler)