@@ -1,4 +1,4 @@
-package gitx +package git import ( "context"@@ -9,14 +9,14 @@ "strings"
) // ArchiveTar generates a tarball of a git ref. -func ArchiveTar(ctx context.Context, repoDir, ref string, out io.Writer) error { +func (g *Repo) ArchiveTar(ctx context.Context, ref string, out io.Writer) error { if !isValidRef(ref) { return fmt.Errorf("invalid ref: %s", ref) } if err := gitCmd(ctx, cmdOpts{ Cmd: []string{"archive", "--format=tar.gz", ref}, - RepoDir: repoDir, + RepoDir: g.path, Stdout: out, }); err != nil { return fmt.Errorf("git archive %s: %w", ref, err)@@ -25,10 +25,10 @@
return nil } -func UploadArchive(ctx context.Context, repoDir string, in io.Reader, out io.Writer) error { +func (g *Repo) UploadArchive(ctx context.Context, in io.Reader, out io.Writer) error { if err := gitCmd(ctx, cmdOpts{ - RepoDir: repoDir, Cmd: []string{"upload-archive"}, + RepoDir: g.path, Stdin: in, Stdout: out, Stderr: out,
@@ -1,4 +1,4 @@
-package gitx +package git import ( "context"
@@ -1,67 +0,0 @@
-package gitx - -import ( - "context" - "fmt" - "io" - "strings" -) - -// InfoRefs executes git-upload-pack --advertise-refs for smart-HTTP discovery. -func InfoRefs(ctx context.Context, repoDir, protocol string, out io.Writer) error { - if !strings.Contains(protocol, "version=2") { - if err := PackLine(out, "# service=git-upload-pack\n"); err != nil { - return fmt.Errorf("write pack line: %w", err) - } - if err := PackFlush(out); err != nil { - return fmt.Errorf("flush pack: %w", err) - } - } - - if err := gitCmd(ctx, cmdOpts{ - RepoDir: repoDir, - Cmd: []string{ - "-c", "uploadpack.allowFilter=true", - "upload-pack", "--stateless-rpc", "--advertise-refs", - }, - Stdout: out, - Stderr: out, // TODO: Check if this is correct. - }); err != nil { - return fmt.Errorf("git-upload-pack: %w", err) - } - return nil -} - -// UploadPack executes git-upload-pack for smart-HTTP git fetch/clone. -// StatelessRPC should be true in case it's used over http, and false for ssh. -func UploadPack(ctx context.Context, repoDir string, statelessRPC bool, in io.Reader, out io.Writer) error { - cmd := []string{"-c", "uploadpack.allowFilter=true", "upload-pack"} - if statelessRPC { - cmd = append(cmd, "--stateless-rpc") - } - - if err := gitCmd(ctx, cmdOpts{ - RepoDir: repoDir, - Cmd: cmd, - Stdin: in, - Stdout: out, - Stderr: out, // TODO: Check if this is correct. - }); err != nil { - return fmt.Errorf("git-upload-pack: %w", err) - } - return nil -} - -// ReceivePack executes git-receive-pack for git push. -func ReceivePack(ctx context.Context, repoDir string, in io.Reader, out, errout io.Writer) error { - if err := gitCmd(ctx, cmdOpts{ - RepoDir: repoDir, - Cmd: []string{"receive-pack"}, - Stdin: in, - Stdout: out, - Stderr: errout, - }); err != nil { - return fmt.Errorf("git-receive-pack: %w", err) - } - return nil -}
@@ -1,24 +0,0 @@
-package gitx - -import ( - "fmt" - "io" -) - -// PackLine writes a pkt-line formatted string. -func PackLine(w io.Writer, s string) error { - _, err := fmt.Fprintf(w, "%04x%s", len(s)+4, s) - return err -} - -// PackFlush writes a flush packet. -func PackFlush(w io.Writer) error { - _, err := fmt.Fprint(w, "0000") - return err -} - -// PackError writes an ERR packet for protocol-level errors. -// Git displays this as: fatal: remote error: <msg> -func PackError(w io.Writer, msg string) error { - return PackLine(w, "ERR "+msg) -}
@@ -0,0 +1,87 @@
+package git + +import ( + "context" + "fmt" + "io" + "strings" +) + +// InfoRefs executes git-upload-pack --advertise-refs for smart-HTTP discovery. +func (g *Repo) InfoRefs(ctx context.Context, protocol string, out io.Writer) error { + if !strings.Contains(protocol, "version=2") { + if err := PackLine(out, "# service=git-upload-pack\n"); err != nil { + return fmt.Errorf("write pack line: %w", err) + } + if err := PackFlush(out); err != nil { + return fmt.Errorf("flush pack: %w", err) + } + } + + if err := gitCmd(ctx, cmdOpts{ + GitProtocol: protocol, + Cmd: []string{ + "-c", "uploadpack.allowFilter=true", + "upload-pack", "--stateless-rpc", "--advertise-refs", + }, + RepoDir: g.path, + Stdout: out, + Stderr: out, // TODO: Check if this is correct. + }); err != nil { + return fmt.Errorf("git-upload-pack: %w", err) + } + return nil +} + +// UploadPack executes git-upload-pack for smart-HTTP git fetch/clone. +// StatelessRPC should be true in case it's used over http, and false for ssh. +func (g *Repo) UploadPack(ctx context.Context, statelessRPC bool, protocol string, in io.Reader, out io.Writer) error { + cmd := []string{"-c", "uploadpack.allowFilter=true", "upload-pack"} + if statelessRPC { + cmd = append(cmd, "--stateless-rpc") + } + + if err := gitCmd(ctx, cmdOpts{ + Cmd: cmd, + GitProtocol: protocol, + RepoDir: g.path, + Stdin: in, + Stdout: out, + Stderr: out, // TODO: Check if this is correct. + }); err != nil { + return fmt.Errorf("git-upload-pack: %w", err) + } + return nil +} + +// ReceivePack executes git-receive-pack for git push. +func (g *Repo) ReceivePack(ctx context.Context, in io.Reader, out, errout io.Writer) error { + if err := gitCmd(ctx, cmdOpts{ + RepoDir: g.path, + Cmd: []string{"receive-pack"}, + Stdin: in, + Stdout: out, + Stderr: errout, + }); err != nil { + return fmt.Errorf("git-receive-pack: %w", err) + } + return nil +} + +// PackLine writes a pkt-line formatted string. +func PackLine(w io.Writer, s string) error { + _, err := fmt.Fprintf(w, "%04x%s", len(s)+4, s) + return err +} + +// PackFlush writes a flush packet. +func PackFlush(w io.Writer) error { + _, err := fmt.Fprint(w, "0000") + return err +} + +// PackError writes an ERR packet for protocol-level errors. +// Git displays this as: fatal: remote error: <msg> +func PackError(w io.Writer, msg string) error { + return PackLine(w, "ERR "+msg) +}
@@ -8,11 +8,10 @@ "log/slog"
"net/http" "olexsmir.xyz/mugit/internal/git" - "olexsmir.xyz/mugit/internal/git/gitx" ) func (h *handlers) infoRefsHandler(w http.ResponseWriter, r *http.Request) { - path, err := h.checkRepoPublicityAndGetPath(r.PathValue("name"), "") + repo, err := h.openPublicRepo(r.PathValue("name"), "") if err != nil { h.gitError(w, http.StatusNotFound, "repository not found") return@@ -27,7 +26,7 @@ w.Header().Set("Connection", "Keep-Alive")
w.Header().Set("Cache-Control", "no-cache, max-age=0, must-revalidate") w.WriteHeader(http.StatusOK) - if err := gitx.InfoRefs(r.Context(), path, gitProtocol, w); err != nil { + if err := repo.InfoRefs(r.Context(), gitProtocol, w); err != nil { h.gitError(w, http.StatusInternalServerError, err.Error()) slog.Error("git: info/refs", "err", err) return@@ -44,7 +43,8 @@
const uploadPackExpectedContentType = "application/x-git-upload-pack-request" func (h *handlers) uploadPackHandler(w http.ResponseWriter, r *http.Request) { - path, err := h.checkRepoPublicityAndGetPath(r.PathValue("name"), "") + gitProtocol := r.Header.Get("Git-Protocol") + repo, err := h.openPublicRepo(r.PathValue("name"), "") if err != nil { h.gitError(w, http.StatusNotFound, "repository not found") return@@ -73,7 +73,7 @@ w.Header().Set("Connection", "Keep-Alive")
w.Header().Set("Cache-Control", "no-cache, max-age=0, must-revalidate") w.WriteHeader(http.StatusOK) - if err := gitx.UploadPack(r.Context(), path, true, bodyReader, newFlushWriter(w)); err != nil { + if err := repo.UploadPack(r.Context(), true, gitProtocol, bodyReader, newFlushWriter(w)); err != nil { h.gitError(w, http.StatusInternalServerError, err.Error()) slog.Error("git: upload-pack", "err", err) return@@ -89,7 +89,7 @@ func (h *handlers) archiveHandler(w http.ResponseWriter, r *http.Request) {
name := r.PathValue("name") ref := h.parseRef(r.PathValue("ref")) - path, err := h.checkRepoPublicityAndGetPath(name, ref) + repo, err := h.openPublicRepo(name, ref) if err != nil { h.write404(w, err) return@@ -100,7 +100,7 @@ w.Header().Set("Content-Disposition", fmt.Sprintf("attachment; filename=%q", filename))
w.Header().Set("Content-Type", "application/gzip") w.WriteHeader(http.StatusOK) - if err := gitx.ArchiveTar(r.Context(), path, ref, w); err != nil { + if err := repo.ArchiveTar(r.Context(), ref, w); err != nil { slog.Error("git: archive", "ref", ref, "err", err) return }@@ -110,20 +110,6 @@ func (h *handlers) gitError(w http.ResponseWriter, code int, msg string) {
w.Header().Set("content-type", "text/plain; charset=UTF-8") w.WriteHeader(code) fmt.Fprintf(w, "%s\n", msg) -} - -func (h *handlers) checkRepoPublicityAndGetPath(name string, ref string) (string, error) { - name = git.ResolveName(name) - path, err := git.ResolvePath(h.c.Repo.Dir, name) - if err != nil { - return "", err - } - - if _, oerr := git.OpenPublic(path, ref); oerr != nil { - return "", oerr - } - - return path, err } func (h *handlers) openPublicRepo(name, ref string) (*git.Repo, error) {
@@ -9,7 +9,6 @@
"github.com/gliderlabs/ssh" "olexsmir.xyz/mugit/internal/config" "olexsmir.xyz/mugit/internal/git" - "olexsmir.xyz/mugit/internal/git/gitx" gossh "golang.org/x/crypto/ssh" )@@ -100,7 +99,7 @@ s.gitError(sess, badRequestErrMsg, nil)
return } - if err := gitx.UploadPack(ctx, repoPath, false, sess, sess); err != nil { + if err := repo.UploadPack(ctx, false, "", sess, sess); err != nil { s.gitError(sess, internalServerErrMsg, err) return }@@ -119,7 +118,7 @@ s.gitError(sess, badRequestErrMsg, nil)
return } - if err := gitx.UploadArchive(ctx, repoPath, sess, sess); err != nil { + if err := repo.UploadArchive(ctx, sess, sess); err != nil { s.gitError(sess, internalServerErrMsg, err) return }@@ -132,7 +131,7 @@ s.gitError(sess, unauthorizedErrMsg, nil)
return } - if err := gitx.ReceivePack(ctx, repoPath, sess, sess, sess.Stderr()); err != nil { + if err := repo.ReceivePack(ctx, sess, sess, sess.Stderr()); err != nil { s.gitError(sess, internalServerErrMsg, err) return }@@ -179,7 +178,7 @@ }
func (s *Server) gitError(sess ssh.Session, msg string, err error) { slog.Error("ssh git error", "msg", msg, "err", err) - gitx.PackError(sess, msg) - gitx.PackFlush(sess) + git.PackError(sess, msg) + git.PackFlush(sess) sess.Exit(1) }