aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGabriel A. Giovanini <mail@gabrielgio.me>2025-03-30 17:34:11 +0200
committerGabriel A. Giovanini <mail@gabrielgio.me>2025-03-30 17:34:11 +0200
commit2c0347566f99afec2e3963d74f4fc970e6187217 (patch)
tree0c63e87823ab595fffcd474143fa10269a4f65fd
parentf19adedc4b7f71c824a59d4a797bd8872bdb0bbd (diff)
downloadcerrado-2c0347566f99afec2e3963d74f4fc970e6187217.tar.gz
cerrado-2c0347566f99afec2e3963d74f4fc970e6187217.tar.bz2
cerrado-2c0347566f99afec2e3963d74f4fc970e6187217.zip
feat: Add initial support for http git cloneHEADv0.2.1master
-rw-r--r--pkg/ext/router.go7
-rw-r--r--pkg/git/git.go93
-rw-r--r--pkg/handler/git/handler.go44
-rw-r--r--pkg/handler/router.go3
-rw-r--r--pkg/service/git.go29
5 files changed, 175 insertions, 1 deletions
diff --git a/pkg/ext/router.go b/pkg/ext/router.go
index ce4c126..434972b 100644
--- a/pkg/ext/router.go
+++ b/pkg/ext/router.go
@@ -69,6 +69,13 @@ func NotFound(w http.ResponseWriter, r *http.Request) {
}, r.Context())
}
+func BadRequest(w http.ResponseWriter, r *http.Request, msg string) {
+ w.WriteHeader(http.StatusBadRequest)
+ templates.WritePageTemplate(w, &templates.ErrorPage{
+ Message: msg,
+ }, r.Context())
+}
+
func Redirect(w http.ResponseWriter, location string) {
w.Header().Add("location", location)
w.WriteHeader(http.StatusTemporaryRedirect)
diff --git a/pkg/git/git.go b/pkg/git/git.go
index 64c721a..95355f3 100644
--- a/pkg/git/git.go
+++ b/pkg/git/git.go
@@ -3,12 +3,17 @@ package git
import (
"archive/tar"
"bytes"
+ "context"
"errors"
"fmt"
"io"
"io/fs"
+ "log"
+ "log/slog"
+ "os/exec"
"path"
"sort"
+ "syscall"
"time"
"github.com/go-git/go-git/v5"
@@ -432,6 +437,66 @@ func (g *GitRepository) FileContent(path string) ([]byte, error) {
return buf.Bytes(), nil
}
+func (g *GitRepository) WriteInfoRefs(ctx context.Context, w io.Writer) error {
+ cmd := exec.CommandContext(
+ ctx,
+ "git-upload-pack",
+ "--stateless-rpc",
+ "--advertise-refs",
+ ".",
+ )
+
+ cmd.Dir = g.path
+ cmd.Stdout = w
+
+ var buff bytes.Buffer
+ cmd.Stderr = &buff
+
+ err := packLine(w, "# service=git-upload-pack\n")
+ if err != nil {
+ return err
+ }
+
+ err = packFlush(w)
+ if err != nil {
+ return err
+ }
+
+ err = cmd.Run()
+ if err != nil {
+ slog.Error("Error upload pack refs", "message", buff.String())
+ return err
+ }
+ return nil
+}
+
+func (g *GitRepository) WriteUploadPack(ctx context.Context, r io.Reader, w io.Writer) error {
+ cmd := exec.CommandContext(
+ ctx,
+ "git-upload-pack",
+ "--stateless-rpc",
+ ".",
+ )
+ cmd.Dir = g.Path()
+ cmd.SysProcAttr = &syscall.SysProcAttr{Setpgid: true}
+ var buff bytes.Buffer
+ cmd.Stderr = &buff
+ cmd.Stdin = r
+ cmd.Stdout = w
+
+ if err := cmd.Start(); err != nil {
+ log.Printf("git: failed to start git-upload-pack: %s", err)
+ return err
+ }
+
+ if err := cmd.Wait(); err != nil {
+ log.Printf("git: failed to wait for git-upload-pack: %s", buff.String())
+ return err
+ }
+
+ return nil
+}
+
func (g *GitRepository) WriteTar(w io.Writer, prefix string) error {
tw := tar.NewWriter(w)
defer tw.Close()
@@ -613,3 +678,31 @@ func (self *tagList) Less(i, j int) bool {
return dateI.After(dateJ)
}
+
+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
+}
+
+type debugReader struct {
+ r io.Reader
+}
+
+func (d *debugReader) Read(p []byte) (n int, err error) {
+ fmt.Printf("READ: %x\n", p)
+ return d.r.Read(p)
+}
+
+type debugWriter struct {
+ w io.Writer
+}
+
+func (d *debugWriter) Write(p []byte) (n int, err error) {
+ fmt.Printf("WRITE: %x\n", p)
+ return d.w.Write(p)
+}
diff --git a/pkg/handler/git/handler.go b/pkg/handler/git/handler.go
index a9be54c..61765bb 100644
--- a/pkg/handler/git/handler.go
+++ b/pkg/handler/git/handler.go
@@ -2,6 +2,7 @@ package git
import (
"bytes"
+ "compress/gzip"
"errors"
"fmt"
"io"
@@ -115,6 +116,49 @@ func (g *GitHandler) Archive(w http.ResponseWriter, r *http.Request) error {
return nil
}
+func (g *GitHandler) Multiplex(w http.ResponseWriter, r *http.Request) error {
+ path := r.PathValue("rest")
+ name := r.PathValue("name")
+
+ if r.URL.RawQuery == "service=git-receive-pack" {
+ ext.BadRequest(w, r, "no pushing allowed")
+ return nil
+ }
+
+ if path == "info/refs" && r.URL.RawQuery == "service=git-upload-pack" && r.Method == "GET" {
+ w.Header().Set("content-type", "application/x-git-upload-pack-advertisement")
+
+ err := g.gitService.WriteInfoRefs(r.Context(), name, w)
+ if err != nil {
+ slog.Error("Error WriteInfoRefs", "error", err)
+ }
+ } else if path == "git-upload-pack" && r.Method == "POST" {
+ 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)
+
+ reader := r.Body
+
+ if r.Header.Get("Content-Encoding") == "gzip" {
+ reader, err := gzip.NewReader(r.Body)
+ if err != nil {
+ return err
+ }
+ defer reader.Close()
+ }
+
+ err := g.gitService.WriteUploadPack(r.Context(), name, reader, w)
+ if err != nil {
+ slog.Error("Error WriteUploadPack", "error", err)
+ }
+ } else if r.Method == "GET" {
+ return g.Summary(w, r)
+ }
+
+ return nil
+}
+
func (g *GitHandler) Summary(w http.ResponseWriter, r *http.Request) error {
ext.SetHTML(w)
name := r.PathValue("name")
diff --git a/pkg/handler/router.go b/pkg/handler/router.go
index e461922..fea8827 100644
--- a/pkg/handler/router.go
+++ b/pkg/handler/router.go
@@ -46,7 +46,8 @@ func MountHandler(
mux.HandleFunc("/static/{file}", staticHandler)
mux.HandleFunc("/{name}/about/{$}", gitHandler.About)
- mux.HandleFunc("/{name}/", gitHandler.Summary)
+ mux.HandleFunc("/{name}", gitHandler.Multiplex)
+ mux.HandleFunc("/{name}/{rest...}", gitHandler.Multiplex)
mux.HandleFunc("/{name}/refs/{$}", gitHandler.Refs)
mux.HandleFunc("/{name}/tree/{ref}/{rest...}", gitHandler.Tree)
mux.HandleFunc("/{name}/blob/{ref}/{rest...}", gitHandler.Blob)
diff --git a/pkg/service/git.go b/pkg/service/git.go
index 5410d7a..6aa5cd6 100644
--- a/pkg/service/git.go
+++ b/pkg/service/git.go
@@ -2,6 +2,7 @@ package service
import (
"compress/gzip"
+ "context"
"errors"
"io"
"log/slog"
@@ -299,3 +300,31 @@ func (g *GitService) GetHead(name string) (*plumbing.Reference, error) {
return repo.Head()
}
+
+func (g *GitService) WriteInfoRefs(ctx context.Context, name string, w io.Writer) error {
+ r := g.configRepo.GetByName(name)
+ if r == nil {
+ return ErrRepositoryNotFound
+ }
+
+ repo, err := git.OpenRepository(r.Path)
+ if err != nil {
+ return err
+ }
+
+ return repo.WriteInfoRefs(ctx, w)
+}
+
+func (g *GitService) WriteUploadPack(ctx context.Context, name string, re io.Reader, w io.Writer) error {
+ r := g.configRepo.GetByName(name)
+ if r == nil {
+ return ErrRepositoryNotFound
+ }
+
+ repo, err := git.OpenRepository(r.Path)
+ if err != nil {
+ return err
+ }
+
+ return repo.WriteUploadPack(ctx, re, w)
+}