A vibe coded tangled fork which supports pijul.
at sl/lvnwqspuwzom 189 lines 5.7 kB view raw
1package knotserver 2 3import ( 4 "compress/gzip" 5 "fmt" 6 "io" 7 "net/http" 8 "os" 9 "strings" 10 11 "github.com/go-chi/chi/v5" 12 "tangled.org/core/knotserver/git/service" 13) 14 15func (h *Knot) InfoRefs(w http.ResponseWriter, r *http.Request) { 16 name := chi.URLParam(r, "name") 17 repoPath, ok := repoPathFromcontext(r.Context()) 18 if !ok { 19 w.WriteHeader(http.StatusInternalServerError) 20 w.Write([]byte("Failed to find repository path")) 21 return 22 } 23 24 cmd := service.ServiceCommand{ 25 GitProtocol: r.Header.Get("Git-Protocol"), 26 Dir: repoPath, 27 Stdout: w, 28 } 29 30 serviceName := r.URL.Query().Get("service") 31 switch serviceName { 32 case "git-upload-pack": 33 w.Header().Set("Content-Type", "application/x-git-upload-pack-advertisement") 34 w.Header().Set("Connection", "Keep-Alive") 35 w.Header().Set("Cache-Control", "no-cache, max-age=0, must-revalidate") 36 w.WriteHeader(http.StatusOK) 37 38 if err := cmd.InfoRefs(); err != nil { 39 gitError(w, err.Error(), http.StatusInternalServerError) 40 h.l.Error("git: process failed", "handler", "InfoRefs", "service", serviceName, "error", err) 41 return 42 } 43 case "git-receive-pack": 44 h.RejectPush(w, r, name) 45 default: 46 gitError(w, fmt.Sprintf("service unsupported: '%s'", serviceName), http.StatusForbidden) 47 } 48} 49 50func (h *Knot) UploadArchive(w http.ResponseWriter, r *http.Request) { 51 repo, ok := repoPathFromcontext(r.Context()) 52 if !ok { 53 w.WriteHeader(http.StatusInternalServerError) 54 w.Write([]byte("Failed to find repository path")) 55 return 56 } 57 58 const expectedContentType = "application/x-git-upload-archive-request" 59 contentType := r.Header.Get("Content-Type") 60 if contentType != expectedContentType { 61 gitError(w, fmt.Sprintf("Expected Content-Type: '%s', but received '%s'.", expectedContentType, contentType), http.StatusUnsupportedMediaType) 62 } 63 64 var bodyReader io.ReadCloser = r.Body 65 if r.Header.Get("Content-Encoding") == "gzip" { 66 gzipReader, err := gzip.NewReader(r.Body) 67 if err != nil { 68 gitError(w, err.Error(), http.StatusInternalServerError) 69 h.l.Error("git: failed to create gzip reader", "handler", "UploadArchive", "error", err) 70 return 71 } 72 defer gzipReader.Close() 73 bodyReader = gzipReader 74 } 75 76 w.Header().Set("Content-Type", "application/x-git-upload-archive-result") 77 78 h.l.Info("git: executing git-upload-archive", "handler", "UploadArchive", "repo", repo) 79 80 cmd := service.ServiceCommand{ 81 GitProtocol: r.Header.Get("Git-Protocol"), 82 Dir: repo, 83 Stdout: w, 84 Stdin: bodyReader, 85 } 86 87 w.WriteHeader(http.StatusOK) 88 89 if err := cmd.UploadArchive(); err != nil { 90 h.l.Error("git: failed to execute git-upload-pack", "handler", "UploadPack", "error", err) 91 return 92 } 93} 94 95func (h *Knot) UploadPack(w http.ResponseWriter, r *http.Request) { 96 repo, ok := repoPathFromcontext(r.Context()) 97 if !ok { 98 w.WriteHeader(http.StatusInternalServerError) 99 w.Write([]byte("Failed to find repository path")) 100 return 101 } 102 103 const expectedContentType = "application/x-git-upload-pack-request" 104 contentType := r.Header.Get("Content-Type") 105 if contentType != expectedContentType { 106 gitError(w, fmt.Sprintf("Expected Content-Type: '%s', but received '%s'.", expectedContentType, contentType), http.StatusUnsupportedMediaType) 107 } 108 109 var bodyReader io.ReadCloser = r.Body 110 if r.Header.Get("Content-Encoding") == "gzip" { 111 gzipReader, err := gzip.NewReader(r.Body) 112 if err != nil { 113 gitError(w, err.Error(), http.StatusInternalServerError) 114 h.l.Error("git: failed to create gzip reader", "handler", "UploadPack", "error", err) 115 return 116 } 117 defer gzipReader.Close() 118 bodyReader = gzipReader 119 } 120 121 w.Header().Set("Content-Type", "application/x-git-upload-pack-result") 122 w.Header().Set("Connection", "Keep-Alive") 123 w.Header().Set("Cache-Control", "no-cache, max-age=0, must-revalidate") 124 125 h.l.Info("git: executing git-upload-pack", "handler", "UploadPack", "repo", repo) 126 127 cmd := service.ServiceCommand{ 128 GitProtocol: r.Header.Get("Git-Protocol"), 129 Dir: repo, 130 Stdout: w, 131 Stdin: bodyReader, 132 } 133 134 w.WriteHeader(http.StatusOK) 135 136 if err := cmd.UploadPack(); err != nil { 137 h.l.Error("git: failed to execute git-upload-pack", "handler", "UploadPack", "error", err) 138 return 139 } 140} 141 142func (h *Knot) ReceivePack(w http.ResponseWriter, r *http.Request) { 143 name := chi.URLParam(r, "name") 144 h.RejectPush(w, r, name) 145} 146 147func (h *Knot) RejectPush(w http.ResponseWriter, r *http.Request, unqualifiedRepoName string) { 148 // A text/plain response will cause git to print each line of the body 149 // prefixed with "remote: ". 150 w.Header().Set("content-type", "text/plain; charset=UTF-8") 151 w.WriteHeader(http.StatusForbidden) 152 153 fmt.Fprintf(w, "Pushes are only supported over SSH.") 154 155 // If the appview gave us the repository owner's handle we can attempt to 156 // construct the correct ssh url. 157 ownerHandle := r.Header.Get("x-tangled-repo-owner-handle") 158 ownerHandle = strings.TrimPrefix(ownerHandle, "@") 159 if ownerHandle != "" && !strings.ContainsAny(ownerHandle, ":") { 160 hostname := h.c.Server.Hostname 161 if strings.Contains(hostname, ":") { 162 hostname = strings.Split(hostname, ":")[0] 163 } 164 165 if hostname == "knot1.tangled.sh" { 166 hostname = "tangled.sh" 167 } 168 169 fmt.Fprintf(w, " Try:\ngit remote set-url --push origin git@%s:%s/%s\n\n... and push again.", hostname, ownerHandle, unqualifiedRepoName) 170 } 171 fmt.Fprintf(w, "\n\n") 172} 173 174func isDir(path string) (bool, error) { 175 info, err := os.Stat(path) 176 if err == nil && info.IsDir() { 177 return true, nil 178 } 179 if os.IsNotExist(err) { 180 return false, nil 181 } 182 return false, err 183} 184 185func gitError(w http.ResponseWriter, msg string, status int) { 186 w.Header().Set("content-type", "text/plain; charset=UTF-8") 187 w.WriteHeader(status) 188 fmt.Fprintf(w, "%s\n", msg) 189}