aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--config.yaml6
-rw-r--r--flake.nix2
-rw-r--r--git/service/service.go121
-rw-r--r--git/service/write_flusher.go25
-rw-r--r--routes/git.go96
5 files changed, 179 insertions, 71 deletions
diff --git a/config.yaml b/config.yaml
index 7333e95..7a6066f 100644
--- a/config.yaml
+++ b/config.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
diff --git a/flake.nix b/flake.nix
index 4880607..da2da41 100644
--- a/flake.nix
+++ b/flake.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" = { }; };
diff --git a/git/service/service.go b/git/service/service.go
new file mode 100644
index 0000000..d2c7fdd
--- /dev/null
+++ b/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
+}
diff --git a/git/service/write_flusher.go b/git/service/write_flusher.go
new file mode 100644
index 0000000..f25a5ae
--- /dev/null
+++ b/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)
+}
diff --git a/routes/git.go b/routes/git.go
index b8877b9..0ebb6d2 100644
--- a/routes/git.go
+++ b/routes/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 @@ func (d *deps) InfoRefs(w http.ResponseWriter, r *http.Request) {
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
- }
-
- billyfs := osfs.New(repo)
- loader := server.NewFilesystemLoader(billyfs)
- srv := server.NewServer(loader)
- session, err := srv.NewUploadPackSession(ep, nil)
- if 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,
+ cmd := service.ServiceCommand{
+ Dir: repo,
+ Stdout: w,
}
- if err = ar.Encode(w); err != nil {
+ if err := cmd.InfoRefs(); 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 @@ func (d *deps) UploadPack(w http.ResponseWriter, r *http.Request) {
repo := filepath.Join(d.c.Repo.ScanPath, name)
w.Header().Set("content-type", "application/x-git-upload-pack-result")
+ w.Header().Set("Connection", "Keep-Alive")
+ w.Header().Set("Transfer-Encoding", "chunked")
+ w.WriteHeader(http.StatusOK)
- upr := packp.NewUploadPackRequest()
- err := upr.Decode(r.Body)
- if err != nil {
- http.Error(w, err.Error(), 400)
- log.Printf("git: %s", err)
- return
- }
-
- ep, err := transport.NewEndpoint("/")
- if err != nil {
- http.Error(w, err.Error(), 500)
- log.Printf("git: %s", err)
- return
- }
-
- 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
+ cmd := service.ServiceCommand{
+ Dir: repo,
+ Stdout: w,
}
- res, err := session.UploadPack(r.Context(), upr)
- if err != nil {
- http.Error(w, err.Error(), 500)
- log.Printf("git: %s", err)
- return
+ var reader io.ReadCloser
+ reader = r.Body
+
+ 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
}
}