refactor(servegit): vendor in lib/gitservice by keegancsmith · Pull Request #1200 · sourcegraph/src-cli
@@ -0,0 +1,173 @@
// package servegit provides a smart Git HTTP transfer protocol handler.
package servegit
import ( "bytes" "compress/gzip" "context" "net/http" "os" "os/exec" "strconv" "strings"
"slices"
"github.com/sourcegraph/sourcegraph/lib/errors" )
var uploadPackArgs = []string{ // Partial clones/fetches "-c", "uploadpack.allowFilter=true",
// Can fetch any object. Used in case of race between a resolve ref and a // fetch of a commit. Safe to do, since this is only used internally. "-c", "uploadpack.allowAnySHA1InWant=true",
// The maximum size of memory that is consumed by each thread in git-pack-objects[1] // for pack window memory when no limit is given on the command line. // // Important for large monorepos to not run into memory issues when cloned. "-c", "pack.windowMemory=100m",
"upload-pack",
"--stateless-rpc", "--strict", }
// Handler is a smart Git HTTP transfer protocol as documented at // https://www.git-scm.com/docs/http-protocol. // // This allows users to clone any git repo. We only support the smart // protocol. We aim to support modern git features such as protocol v2 to // minimize traffic. type Handler struct { // Dir is a function which takes a repository name and returns an absolute // path to the GIT_DIR for it. Dir func(context.Context, string) (string, error)
// ErrorHook is called if we fail to run the git command. The main use of // this is to inject logging. For example in src-cli we don't use // sourcegraph/log so this allows us to use stdlib log. // // Note: This is required to be set ErrorHook func(err error, stderr string)
// CommandHook if non-nil will run with the git upload command before we // start the command. // // This allows the command to be modified before running. In practice // sourcegraph.com will add a flowrated writer for Stdout to treat our // internal networks more kindly. CommandHook func(*exec.Cmd)
// Trace if non-nil is called at the start of serving a request. It will // call the returned function when done executing. If the executation // failed, it will pass in a non-nil error. Trace func(ctx context.Context, svc, repo, protocol string) func(error) }
func (s *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) { ctx := r.Context()
// Only support clones and fetches (git upload-pack). /info/refs sets the // service field. if svcQ := r.URL.Query().Get("service"); svcQ != "" && svcQ != "git-upload-pack" { http.Error(w, "only support service git-upload-pack", http.StatusBadRequest) return }
var repo, svc string for _, suffix := range []string{"/info/refs", "/git-upload-pack"} { if strings.HasSuffix(r.URL.Path, suffix) { svc = suffix repo = strings.TrimSuffix(r.URL.Path, suffix) repo = strings.TrimPrefix(repo, "/") break } }
dir, err := s.Dir(ctx, repo) if err != nil { http.Error(w, "failed to determine repository path: "+err.Error(), http.StatusInternalServerError) return }
if _, err = os.Stat(dir); os.IsNotExist(err) { http.Error(w, "repository not found", http.StatusNotFound) return } else if err != nil { http.Error(w, "failed to stat repo: "+err.Error(), http.StatusInternalServerError) return }
body := r.Body defer body.Close()
if r.Header.Get("Content-Encoding") == "gzip" { gzipReader, err := gzip.NewReader(body) if err != nil { http.Error(w, "malformed payload: "+err.Error(), http.StatusBadRequest) return } defer gzipReader.Close()
body = gzipReader }
// err is set if we fail to run command or have an unexpected svc. It is // captured for tracing. if s.Trace != nil { done := s.Trace(ctx, svc, repo, r.Header.Get("Git-Protocol")) defer func() { done(err) }() }
args := slices.Clone(uploadPackArgs) switch svc { case "/info/refs": w.Header().Set("Content-Type", "application/x-git-upload-pack-advertisement") _, _ = w.Write(packetWrite("# service=git-upload-pack\n")) _, _ = w.Write([]byte("0000")) args = append(args, "--advertise-refs") case "/git-upload-pack": w.Header().Set("Content-Type", "application/x-git-upload-pack-result") default: err = errors.Errorf("unexpected subpath (want /info/refs or /git-upload-pack): %q", svc) http.Error(w, err.Error(), http.StatusInternalServerError) return } args = append(args, dir)
env := os.Environ() if protocol := r.Header.Get("Git-Protocol"); protocol != "" { env = append(env, "GIT_PROTOCOL="+protocol) }
var stderr bytes.Buffer cmd := exec.CommandContext(ctx, "git", args...) cmd.Env = env cmd.Stdout = w cmd.Stderr = &stderr cmd.Stdin = body
if s.CommandHook != nil { s.CommandHook(cmd) }
err = cmd.Run() if err != nil { err = errors.Errorf("error running git service command args=%q: %w", args, err) s.ErrorHook(err, stderr.String()) _, _ = w.Write([]byte("\n" + err.Error() + "\n")) } }
func packetWrite(str string) []byte { s := strconv.FormatInt(int64(len(str)+4), 16) if len(s)%4 != 0 { s = strings.Repeat("0", 4-len(s)%4) + s } return []byte(s + str) }
import ( "bytes" "compress/gzip" "context" "net/http" "os" "os/exec" "strconv" "strings"
"slices"
"github.com/sourcegraph/sourcegraph/lib/errors" )
var uploadPackArgs = []string{ // Partial clones/fetches "-c", "uploadpack.allowFilter=true",
// Can fetch any object. Used in case of race between a resolve ref and a // fetch of a commit. Safe to do, since this is only used internally. "-c", "uploadpack.allowAnySHA1InWant=true",
// The maximum size of memory that is consumed by each thread in git-pack-objects[1] // for pack window memory when no limit is given on the command line. // // Important for large monorepos to not run into memory issues when cloned. "-c", "pack.windowMemory=100m",
"upload-pack",
"--stateless-rpc", "--strict", }
// Handler is a smart Git HTTP transfer protocol as documented at // https://www.git-scm.com/docs/http-protocol. // // This allows users to clone any git repo. We only support the smart // protocol. We aim to support modern git features such as protocol v2 to // minimize traffic. type Handler struct { // Dir is a function which takes a repository name and returns an absolute // path to the GIT_DIR for it. Dir func(context.Context, string) (string, error)
// ErrorHook is called if we fail to run the git command. The main use of // this is to inject logging. For example in src-cli we don't use // sourcegraph/log so this allows us to use stdlib log. // // Note: This is required to be set ErrorHook func(err error, stderr string)
// CommandHook if non-nil will run with the git upload command before we // start the command. // // This allows the command to be modified before running. In practice // sourcegraph.com will add a flowrated writer for Stdout to treat our // internal networks more kindly. CommandHook func(*exec.Cmd)
// Trace if non-nil is called at the start of serving a request. It will // call the returned function when done executing. If the executation // failed, it will pass in a non-nil error. Trace func(ctx context.Context, svc, repo, protocol string) func(error) }
func (s *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) { ctx := r.Context()
// Only support clones and fetches (git upload-pack). /info/refs sets the // service field. if svcQ := r.URL.Query().Get("service"); svcQ != "" && svcQ != "git-upload-pack" { http.Error(w, "only support service git-upload-pack", http.StatusBadRequest) return }
var repo, svc string for _, suffix := range []string{"/info/refs", "/git-upload-pack"} { if strings.HasSuffix(r.URL.Path, suffix) { svc = suffix repo = strings.TrimSuffix(r.URL.Path, suffix) repo = strings.TrimPrefix(repo, "/") break } }
dir, err := s.Dir(ctx, repo) if err != nil { http.Error(w, "failed to determine repository path: "+err.Error(), http.StatusInternalServerError) return }
if _, err = os.Stat(dir); os.IsNotExist(err) { http.Error(w, "repository not found", http.StatusNotFound) return } else if err != nil { http.Error(w, "failed to stat repo: "+err.Error(), http.StatusInternalServerError) return }
body := r.Body defer body.Close()
if r.Header.Get("Content-Encoding") == "gzip" { gzipReader, err := gzip.NewReader(body) if err != nil { http.Error(w, "malformed payload: "+err.Error(), http.StatusBadRequest) return } defer gzipReader.Close()
body = gzipReader }
// err is set if we fail to run command or have an unexpected svc. It is // captured for tracing. if s.Trace != nil { done := s.Trace(ctx, svc, repo, r.Header.Get("Git-Protocol")) defer func() { done(err) }() }
args := slices.Clone(uploadPackArgs) switch svc { case "/info/refs": w.Header().Set("Content-Type", "application/x-git-upload-pack-advertisement") _, _ = w.Write(packetWrite("# service=git-upload-pack\n")) _, _ = w.Write([]byte("0000")) args = append(args, "--advertise-refs") case "/git-upload-pack": w.Header().Set("Content-Type", "application/x-git-upload-pack-result") default: err = errors.Errorf("unexpected subpath (want /info/refs or /git-upload-pack): %q", svc) http.Error(w, err.Error(), http.StatusInternalServerError) return } args = append(args, dir)
env := os.Environ() if protocol := r.Header.Get("Git-Protocol"); protocol != "" { env = append(env, "GIT_PROTOCOL="+protocol) }
var stderr bytes.Buffer cmd := exec.CommandContext(ctx, "git", args...) cmd.Env = env cmd.Stdout = w cmd.Stderr = &stderr cmd.Stdin = body
if s.CommandHook != nil { s.CommandHook(cmd) }
err = cmd.Run() if err != nil { err = errors.Errorf("error running git service command args=%q: %w", args, err) s.ErrorHook(err, stderr.String()) _, _ = w.Write([]byte("\n" + err.Error() + "\n")) } }
func packetWrite(str string) []byte { s := strconv.FormatInt(int64(len(str)+4), 16) if len(s)%4 != 0 { s = strings.Repeat("0", 4-len(s)%4) + s } return []byte(s + str) }