scripts/
static/
templates/
.gitignore
30 B
.gitsigners
112 B
AGENTS.md
7.4 KiB
LICENSE
89 B
README.md
1.9 KiB
deploy
723 B
discuss.go
16.7 KiB
git.go
3.7 KiB
git_cli.go
16.3 KiB
git_http.go
2.5 KiB
go.mod
664 B
go.sum
5.2 KiB
handler.go
10.8 KiB
handler_test.go
72.1 KiB
highlight.go
5.3 KiB
main.go
5.7 KiB
template.go
8.6 KiB
watch
272 B
git_http.go
raw
| 1 | package main |
| 2 | |
| 3 | import ( |
| 4 | "net/http" |
| 5 | "net/http/cgi" |
| 6 | "os/exec" |
| 7 | "strings" |
| 8 | ) |
| 9 | |
| 10 | const ( |
| 11 | gitHTTPDeltaBaseCacheLimit = "32m" |
| 12 | gitHTTPMaxConcurrent = 8 |
| 13 | ) |
| 14 | |
| 15 | func gitHTTPEnv() []string { |
| 16 | return []string{ |
| 17 | "GIT_PROJECT_ROOT=", |
| 18 | "GIT_HTTP_EXPORT_ALL=1", |
| 19 | "GIT_CONFIG_COUNT=3", |
| 20 | "GIT_CONFIG_KEY_0=safe.directory", |
| 21 | "GIT_CONFIG_VALUE_0=*", |
| 22 | "GIT_CONFIG_KEY_1=pack.threads", |
| 23 | "GIT_CONFIG_VALUE_1=1", |
| 24 | "GIT_CONFIG_KEY_2=core.deltaBaseCacheLimit", |
| 25 | "GIT_CONFIG_VALUE_2=" + gitHTTPDeltaBaseCacheLimit, |
| 26 | } |
| 27 | } |
| 28 | |
| 29 | // handleGitHTTP serves Git smart HTTP protocol requests by proxying to git-http-backend. |
| 30 | // This enables `git clone` over HTTPS. |
| 31 | func (s *server) handleGitHTTP(w http.ResponseWriter, r *http.Request, repo *RepoInfo) { |
| 32 | select { |
| 33 | case s.gitHTTPSlots <- struct{}{}: |
| 34 | defer func() { <-s.gitHTTPSlots }() |
| 35 | default: |
| 36 | w.Header().Set("Retry-After", "1") |
| 37 | http.Error(w, "too many concurrent Git operations", http.StatusServiceUnavailable) |
| 38 | return |
| 39 | } |
| 40 | gitHTTPBackend, err := exec.LookPath("git-http-backend") |
| 41 | if err != nil { |
| 42 | gitExecPath, err2 := exec.Command("git", "--exec-path").Output() |
| 43 | if err2 != nil { |
| 44 | http.Error(w, "git-http-backend not found", http.StatusInternalServerError) |
| 45 | return |
| 46 | } |
| 47 | gitHTTPBackend = strings.TrimSpace(string(gitExecPath)) + "/git-http-backend" |
| 48 | } |
| 49 | |
| 50 | // Strip the base URL prefix and repo name to get the path info that git-http-backend expects. |
| 51 | // e.g. /baseurl/repo/info/refs -> /repo.git/info/refs |
| 52 | pathInfo := r.URL.Path |
| 53 | if s.baseURL != "" { |
| 54 | pathInfo = strings.TrimPrefix(pathInfo, s.baseURL) |
| 55 | } |
| 56 | // Replace /<reponame>/ with /<reponame>.git/ |
| 57 | pathInfo = strings.Replace(pathInfo, "/"+repo.Name+"/", "/"+repo.Name+".git/", 1) |
| 58 | |
| 59 | env := gitHTTPEnv() |
| 60 | env[0] += s.scanPath |
| 61 | handler := &cgi.Handler{ |
| 62 | Path: gitHTTPBackend, |
| 63 | Env: env, |
| 64 | InheritEnv: []string{"PATH"}, |
| 65 | } |
| 66 | |
| 67 | // Override PATH_INFO for the CGI handler. |
| 68 | r2 := r.Clone(r.Context()) |
| 69 | r2.URL.Path = pathInfo |
| 70 | |
| 71 | handler.ServeHTTP(w, r2) |
| 72 | } |
| 73 | |
| 74 | // isGitHTTPRequest returns true if the request is a Git smart HTTP protocol request. |
| 75 | func isGitHTTPRequest(r *http.Request) bool { |
| 76 | query := r.URL.RawQuery |
| 77 | |
| 78 | if strings.HasSuffix(r.URL.Path, "/info/refs") && |
| 79 | (strings.Contains(query, "service=git-upload-pack") || strings.Contains(query, "service=git-receive-pack")) { |
| 80 | return true |
| 81 | } |
| 82 | if strings.HasSuffix(r.URL.Path, "/git-upload-pack") || strings.HasSuffix(r.URL.Path, "/git-receive-pack") { |
| 83 | return true |
| 84 | } |
| 85 | if strings.HasSuffix(r.URL.Path, "/HEAD") || strings.Contains(r.URL.Path, "/objects/") { |
| 86 | return true |
| 87 | } |
| 88 | return false |
| 89 | } |