all repos — elgit @ fba146ac6867b13c40802c4d7a21a8a32571473c

fork of legit: web frontend for git, written in go

git: use system's git upload-pack

This is an intermediate workaround for
https://github.com/go-git/go-git/issues/1062. This should also fix #33.
Anirudh Oppiliappan x@icyphox.sh
Sat, 13 Jul 2024 20:06:37 +0300
commit

fba146ac6867b13c40802c4d7a21a8a32571473c

parent

67e355d5a4f7042d6c16a32afc9119b844d0b467

5 files changed, 178 insertions(+), 70 deletions(-)

jump to
M config.yamlconfig.yaml
@@ -12,9 +12,9 @@ dirs:   templates: ./templates
   static: ./static
 meta:
-  title: git good
-  description: i think it's a skill issue
+  title: icy does git
+  description: come get your free software
 server:
   name: git.icyphox.sh
-  host: 127.0.0.1
+  host: 0.0.0.0
   port: 5555
M flake.nixflake.nix
@@ -38,7 +38,7 @@ };           docker = pkgs.dockerTools.buildLayeredImage {
             name = "sini:5000/legit";
             tag = "latest";
-            contents = [ files legit ];
+            contents = [ files legit pkgs.git ];
             config = {
               Entrypoint = [ "${legit}/bin/legit" ];
               ExposedPorts = { "5555/tcp" = { }; };
A git/service/service.go
@@ -0,0 +1,121 @@+package service
+
+import (
+	"bytes"
+	"fmt"
+	"io"
+	"log"
+	"net/http"
+	"os/exec"
+	"strings"
+	"syscall"
+)
+
+// Mostly from charmbracelet/soft-serve and sosedoff/gitkit.
+
+type ServiceCommand struct {
+	Dir    string
+	Stdin  io.Reader
+	Stdout http.ResponseWriter
+}
+
+func (c *ServiceCommand) InfoRefs() error {
+	cmd := exec.Command("git", []string{
+		"upload-pack",
+		"--stateless-rpc",
+		"--advertise-refs",
+		".",
+	}...)
+
+	cmd.Dir = c.Dir
+	cmd.SysProcAttr = &syscall.SysProcAttr{Setpgid: true}
+	stdoutPipe, _ := cmd.StdoutPipe()
+	cmd.Stderr = cmd.Stdout
+
+	if err := cmd.Start(); err != nil {
+		log.Printf("git: failed to start git-upload-pack (info/refs): %s", err)
+		return err
+	}
+
+	if err := packLine(c.Stdout, "# service=git-upload-pack\n"); err != nil {
+		log.Printf("git: failed to write pack line: %s", err)
+		return err
+	}
+
+	if err := packFlush(c.Stdout); err != nil {
+		log.Printf("git: failed to flush pack: %s", err)
+		return err
+	}
+
+	buf := bytes.Buffer{}
+	if _, err := io.Copy(&buf, stdoutPipe); err != nil {
+		log.Printf("git: failed to copy stdout to tmp buffer: %s", err)
+		return err
+	}
+
+	if err := cmd.Wait(); err != nil {
+		out := strings.Builder{}
+		_, _ = io.Copy(&out, &buf)
+		log.Printf("git: failed to run git-upload-pack; err: %s; output: %s", err, out.String())
+		return err
+	}
+
+	if _, err := io.Copy(c.Stdout, &buf); err != nil {
+		log.Printf("git: failed to copy stdout: %s", err)
+	}
+
+	return nil
+}
+
+func (c *ServiceCommand) UploadPack() error {
+	cmd := exec.Command("git", []string{
+		"-c", "uploadpack.allowFilter=true",
+		"upload-pack",
+		"--stateless-rpc",
+		".",
+	}...)
+	cmd.Dir = c.Dir
+	cmd.SysProcAttr = &syscall.SysProcAttr{Setpgid: true}
+
+	stdoutPipe, _ := cmd.StdoutPipe()
+	cmd.Stderr = cmd.Stdout
+	defer stdoutPipe.Close()
+
+	stdinPipe, err := cmd.StdinPipe()
+	if err != nil {
+		return err
+	}
+	defer stdinPipe.Close()
+
+	if err := cmd.Start(); err != nil {
+		log.Printf("git: failed to start git-upload-pack: %s", err)
+		return err
+	}
+
+	if _, err := io.Copy(stdinPipe, c.Stdin); err != nil {
+		log.Printf("git: failed to copy stdin: %s", err)
+		return err
+	}
+	stdinPipe.Close()
+
+	if _, err := io.Copy(newWriteFlusher(c.Stdout), stdoutPipe); err != nil {
+		log.Printf("git: failed to copy stdout: %s", err)
+		return err
+	}
+	if err := cmd.Wait(); err != nil {
+		log.Printf("git: failed to wait for git-upload-pack: %s", err)
+		return err
+	}
+
+	return nil
+}
+
+func packLine(w io.Writer, s string) error {
+	_, err := fmt.Fprintf(w, "%04x%s", len(s)+4, s)
+	return err
+}
+
+func packFlush(w io.Writer) error {
+	_, err := fmt.Fprint(w, "0000")
+	return err
+}
A git/service/write_flusher.go
@@ -0,0 +1,25 @@+package service
+
+import (
+	"io"
+	"net/http"
+)
+
+func newWriteFlusher(w http.ResponseWriter) io.Writer {
+	return writeFlusher{w.(interface {
+		io.Writer
+		http.Flusher
+	})}
+}
+
+type writeFlusher struct {
+	wf interface {
+		io.Writer
+		http.Flusher
+	}
+}
+
+func (w writeFlusher) Write(p []byte) (int, error) {
+	defer w.wf.Flush()
+	return w.wf.Write(p)
+}
M routes/git.goroutes/git.go
@@ -1,16 +1,13 @@ package routes
 
 import (
-	"errors"
+	"compress/gzip"
+	"io"
 	"log"
 	"net/http"
 	"path/filepath"
 
-	"github.com/go-git/go-billy/v5/osfs"
-	"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"
+	"git.icyphox.sh/legit/git/service"
 )
 
 func (d *deps) InfoRefs(w http.ResponseWriter, r *http.Request) {
@@ -20,41 +17,16 @@ 	repo := filepath.Join(d.c.Repo.ScanPath, name)
 
 	w.Header().Set("content-type", "application/x-git-upload-pack-advertisement")
+	w.WriteHeader(http.StatusOK)
 
-	ep, err := transport.NewEndpoint("/")
-	if err != nil {
-		http.Error(w, err.Error(), 500)
-		log.Printf("git: %s", err)
-		return
+	cmd := service.ServiceCommand{
+		Dir:    repo,
+		Stdout: w,
 	}
 
-	billyfs := osfs.New(repo)
-	loader := server.NewFilesystemLoader(billyfs)
-	srv := server.NewServer(loader)
-	session, err := srv.NewUploadPackSession(ep, nil)
-	if err != nil {
+	if err := cmd.InfoRefs(); err != nil {
 		http.Error(w, err.Error(), 500)
-		log.Printf("git: %s", err)
-		return
-	}
-
-	ar, err := session.AdvertisedReferencesContext(r.Context())
-	if errors.Is(err, transport.ErrRepositoryNotFound) {
-		http.Error(w, err.Error(), 404)
-		return
-	} else if err != nil {
-		http.Error(w, err.Error(), 500)
-		return
-	}
-
-	ar.Prefix = [][]byte{
-		[]byte("# service=git-upload-pack"),
-		pktline.Flush,
-	}
-
-	if err = ar.Encode(w); err != nil {
-		http.Error(w, err.Error(), 500)
-		log.Printf("git: %s", err)
+		log.Printf("git: failed to execute git-upload-pack (info/refs) %s", err)
 		return
 	}
 }
@@ -66,42 +38,32 @@ 	repo := filepath.Join(d.c.Repo.ScanPath, name)
 
 	w.Header().Set("content-type", "application/x-git-upload-pack-result")
-
-	upr := packp.NewUploadPackRequest()
-	err := upr.Decode(r.Body)
-	if err != nil {
-		http.Error(w, err.Error(), 400)
-		log.Printf("git: %s", err)
-		return
-	}
+	w.Header().Set("Connection", "Keep-Alive")
+	w.Header().Set("Transfer-Encoding", "chunked")
+	w.WriteHeader(http.StatusOK)
 
-	ep, err := transport.NewEndpoint("/")
-	if err != nil {
-		http.Error(w, err.Error(), 500)
-		log.Printf("git: %s", err)
-		return
+	cmd := service.ServiceCommand{
+		Dir:    repo,
+		Stdout: w,
 	}
 
-	billyfs := osfs.New(repo)
-	loader := server.NewFilesystemLoader(billyfs)
-	svr := server.NewServer(loader)
-	session, err := svr.NewUploadPackSession(ep, nil)
-	if err != nil {
-		http.Error(w, err.Error(), 500)
-		log.Printf("git: %s", err)
-		return
-	}
+	var reader io.ReadCloser
+	reader = r.Body
 
-	res, err := session.UploadPack(r.Context(), upr)
-	if err != nil {
-		http.Error(w, err.Error(), 500)
-		log.Printf("git: %s", err)
-		return
+	if r.Header.Get("Content-Encoding") == "gzip" {
+		reader, err := gzip.NewReader(r.Body)
+		if err != nil {
+			http.Error(w, err.Error(), 500)
+			log.Printf("git: failed to create gzip reader: %s", err)
+			return
+		}
+		defer reader.Close()
 	}
 
-	if err = res.Encode(w); err != nil {
+	cmd.Stdin = reader
+	if err := cmd.UploadPack(); err != nil {
 		http.Error(w, err.Error(), 500)
-		log.Printf("git: %s", err)
+		log.Printf("git: failed to execute git-upload-pack %s", err)
 		return
 	}
 }