7 files changed,
105 insertions(+),
124 deletions(-)
Author:
Oleksandr Smirnov
olexsmir@gmail.com
Committed at:
2026-03-06 15:15:29 +0200
Authored at:
2026-03-05 21:22:48 +0200
Change ID:
unpqsoqpqorxptnslrsprmyputmvlwwp
Parent:
43dfa27
M
internal/git/gitx/archive.go
→ internal/git/archive.go
路路路 1 -package gitx 1 +package git 2 2 3 3 import ( 4 4 "context" 路路路 9 9 ) 10 10 11 11 // ArchiveTar generates a tarball of a git ref. 12 -func ArchiveTar(ctx context.Context, repoDir, ref string, out io.Writer) error { 12 +func (g *Repo) ArchiveTar(ctx context.Context, ref string, out io.Writer) error { 13 13 if !isValidRef(ref) { 14 14 return fmt.Errorf("invalid ref: %s", ref) 15 15 } 16 16 17 17 if err := gitCmd(ctx, cmdOpts{ 18 18 Cmd: []string{"archive", "--format=tar.gz", ref}, 19 - RepoDir: repoDir, 19 + RepoDir: g.path, 20 20 Stdout: out, 21 21 }); err != nil { 22 22 return fmt.Errorf("git archive %s: %w", ref, err) 路路路 25 25 return nil 26 26 } 27 27 28 -func UploadArchive(ctx context.Context, repoDir string, in io.Reader, out io.Writer) error { 28 +func (g *Repo) UploadArchive(ctx context.Context, in io.Reader, out io.Writer) error { 29 29 if err := gitCmd(ctx, cmdOpts{ 30 - RepoDir: repoDir, 31 30 Cmd: []string{"upload-archive"}, 31 + RepoDir: g.path, 32 32 Stdin: in, 33 33 Stdout: out, 34 34 Stderr: out,
M
internal/git/gitx/gitx.go
→ internal/git/external.go
路路路 1 -package gitx 1 +package git 2 2 3 3 import ( 4 4 "context"
D
internal/git/gitx/pack.go
路路路 1 -package gitx 2 - 3 -import ( 4 - "context" 5 - "fmt" 6 - "io" 7 - "strings" 8 -) 9 - 10 -// InfoRefs executes git-upload-pack --advertise-refs for smart-HTTP discovery. 11 -func InfoRefs(ctx context.Context, repoDir, protocol string, out io.Writer) error { 12 - if !strings.Contains(protocol, "version=2") { 13 - if err := PackLine(out, "# service=git-upload-pack\n"); err != nil { 14 - return fmt.Errorf("write pack line: %w", err) 15 - } 16 - if err := PackFlush(out); err != nil { 17 - return fmt.Errorf("flush pack: %w", err) 18 - } 19 - } 20 - 21 - if err := gitCmd(ctx, cmdOpts{ 22 - RepoDir: repoDir, 23 - Cmd: []string{ 24 - "-c", "uploadpack.allowFilter=true", 25 - "upload-pack", "--stateless-rpc", "--advertise-refs", 26 - }, 27 - Stdout: out, 28 - Stderr: out, // TODO: Check if this is correct. 29 - }); err != nil { 30 - return fmt.Errorf("git-upload-pack: %w", err) 31 - } 32 - return nil 33 -} 34 - 35 -// UploadPack executes git-upload-pack for smart-HTTP git fetch/clone. 36 -// StatelessRPC should be true in case it's used over http, and false for ssh. 37 -func UploadPack(ctx context.Context, repoDir string, statelessRPC bool, in io.Reader, out io.Writer) error { 38 - cmd := []string{"-c", "uploadpack.allowFilter=true", "upload-pack"} 39 - if statelessRPC { 40 - cmd = append(cmd, "--stateless-rpc") 41 - } 42 - 43 - if err := gitCmd(ctx, cmdOpts{ 44 - RepoDir: repoDir, 45 - Cmd: cmd, 46 - Stdin: in, 47 - Stdout: out, 48 - Stderr: out, // TODO: Check if this is correct. 49 - }); err != nil { 50 - return fmt.Errorf("git-upload-pack: %w", err) 51 - } 52 - return nil 53 -} 54 - 55 -// ReceivePack executes git-receive-pack for git push. 56 -func ReceivePack(ctx context.Context, repoDir string, in io.Reader, out, errout io.Writer) error { 57 - if err := gitCmd(ctx, cmdOpts{ 58 - RepoDir: repoDir, 59 - Cmd: []string{"receive-pack"}, 60 - Stdin: in, 61 - Stdout: out, 62 - Stderr: errout, 63 - }); err != nil { 64 - return fmt.Errorf("git-receive-pack: %w", err) 65 - } 66 - return nil 67 -}
D
internal/git/gitx/protocol.go
路路路 1 -package gitx 2 - 3 -import ( 4 - "fmt" 5 - "io" 6 -) 7 - 8 -// PackLine writes a pkt-line formatted string. 9 -func PackLine(w io.Writer, s string) error { 10 - _, err := fmt.Fprintf(w, "%04x%s", len(s)+4, s) 11 - return err 12 -} 13 - 14 -// PackFlush writes a flush packet. 15 -func PackFlush(w io.Writer) error { 16 - _, err := fmt.Fprint(w, "0000") 17 - return err 18 -} 19 - 20 -// PackError writes an ERR packet for protocol-level errors. 21 -// Git displays this as: fatal: remote error: <msg> 22 -func PackError(w io.Writer, msg string) error { 23 - return PackLine(w, "ERR "+msg) 24 -}
A
internal/git/pack.go
路路路 1 +package git 2 + 3 +import ( 4 + "context" 5 + "fmt" 6 + "io" 7 + "strings" 8 +) 9 + 10 +// InfoRefs executes git-upload-pack --advertise-refs for smart-HTTP discovery. 11 +func (g *Repo) InfoRefs(ctx context.Context, protocol string, out io.Writer) error { 12 + if !strings.Contains(protocol, "version=2") { 13 + if err := PackLine(out, "# service=git-upload-pack\n"); err != nil { 14 + return fmt.Errorf("write pack line: %w", err) 15 + } 16 + if err := PackFlush(out); err != nil { 17 + return fmt.Errorf("flush pack: %w", err) 18 + } 19 + } 20 + 21 + if err := gitCmd(ctx, cmdOpts{ 22 + GitProtocol: protocol, 23 + Cmd: []string{ 24 + "-c", "uploadpack.allowFilter=true", 25 + "upload-pack", "--stateless-rpc", "--advertise-refs", 26 + }, 27 + RepoDir: g.path, 28 + Stdout: out, 29 + Stderr: out, // TODO: Check if this is correct. 30 + }); err != nil { 31 + return fmt.Errorf("git-upload-pack: %w", err) 32 + } 33 + return nil 34 +} 35 + 36 +// UploadPack executes git-upload-pack for smart-HTTP git fetch/clone. 37 +// StatelessRPC should be true in case it's used over http, and false for ssh. 38 +func (g *Repo) UploadPack(ctx context.Context, statelessRPC bool, protocol string, in io.Reader, out io.Writer) error { 39 + cmd := []string{"-c", "uploadpack.allowFilter=true", "upload-pack"} 40 + if statelessRPC { 41 + cmd = append(cmd, "--stateless-rpc") 42 + } 43 + 44 + if err := gitCmd(ctx, cmdOpts{ 45 + Cmd: cmd, 46 + GitProtocol: protocol, 47 + RepoDir: g.path, 48 + Stdin: in, 49 + Stdout: out, 50 + Stderr: out, // TODO: Check if this is correct. 51 + }); err != nil { 52 + return fmt.Errorf("git-upload-pack: %w", err) 53 + } 54 + return nil 55 +} 56 + 57 +// ReceivePack executes git-receive-pack for git push. 58 +func (g *Repo) ReceivePack(ctx context.Context, in io.Reader, out, errout io.Writer) error { 59 + if err := gitCmd(ctx, cmdOpts{ 60 + RepoDir: g.path, 61 + Cmd: []string{"receive-pack"}, 62 + Stdin: in, 63 + Stdout: out, 64 + Stderr: errout, 65 + }); err != nil { 66 + return fmt.Errorf("git-receive-pack: %w", err) 67 + } 68 + return nil 69 +} 70 + 71 +// PackLine writes a pkt-line formatted string. 72 +func PackLine(w io.Writer, s string) error { 73 + _, err := fmt.Fprintf(w, "%04x%s", len(s)+4, s) 74 + return err 75 +} 76 + 77 +// PackFlush writes a flush packet. 78 +func PackFlush(w io.Writer) error { 79 + _, err := fmt.Fprint(w, "0000") 80 + return err 81 +} 82 + 83 +// PackError writes an ERR packet for protocol-level errors. 84 +// Git displays this as: fatal: remote error: <msg> 85 +func PackError(w io.Writer, msg string) error { 86 + return PackLine(w, "ERR "+msg) 87 +}
M
internal/handlers/git.go
路路路 8 8 "net/http" 9 9 10 10 "olexsmir.xyz/mugit/internal/git" 11 - "olexsmir.xyz/mugit/internal/git/gitx" 12 11 ) 13 12 14 13 func (h *handlers) infoRefsHandler(w http.ResponseWriter, r *http.Request) { 15 - path, err := h.checkRepoPublicityAndGetPath(r.PathValue("name"), "") 14 + repo, err := h.openPublicRepo(r.PathValue("name"), "") 16 15 if err != nil { 17 16 h.gitError(w, http.StatusNotFound, "repository not found") 18 17 return 路路路 27 26 w.Header().Set("Cache-Control", "no-cache, max-age=0, must-revalidate") 28 27 29 28 w.WriteHeader(http.StatusOK) 30 - if err := gitx.InfoRefs(r.Context(), path, gitProtocol, w); err != nil { 29 + if err := repo.InfoRefs(r.Context(), gitProtocol, w); err != nil { 31 30 h.gitError(w, http.StatusInternalServerError, err.Error()) 32 31 slog.Error("git: info/refs", "err", err) 33 32 return 路路路 44 43 const uploadPackExpectedContentType = "application/x-git-upload-pack-request" 45 44 46 45 func (h *handlers) uploadPackHandler(w http.ResponseWriter, r *http.Request) { 47 - path, err := h.checkRepoPublicityAndGetPath(r.PathValue("name"), "") 46 + gitProtocol := r.Header.Get("Git-Protocol") 47 + repo, err := h.openPublicRepo(r.PathValue("name"), "") 48 48 if err != nil { 49 49 h.gitError(w, http.StatusNotFound, "repository not found") 50 50 return 路路路 73 73 w.Header().Set("Cache-Control", "no-cache, max-age=0, must-revalidate") 74 74 75 75 w.WriteHeader(http.StatusOK) 76 - if err := gitx.UploadPack(r.Context(), path, true, bodyReader, newFlushWriter(w)); err != nil { 76 + if err := repo.UploadPack(r.Context(), true, gitProtocol, bodyReader, newFlushWriter(w)); err != nil { 77 77 h.gitError(w, http.StatusInternalServerError, err.Error()) 78 78 slog.Error("git: upload-pack", "err", err) 79 79 return 路路路 89 89 name := r.PathValue("name") 90 90 ref := h.parseRef(r.PathValue("ref")) 91 91 92 - path, err := h.checkRepoPublicityAndGetPath(name, ref) 92 + repo, err := h.openPublicRepo(name, ref) 93 93 if err != nil { 94 94 h.write404(w, err) 95 95 return 路路路 100 100 w.Header().Set("Content-Type", "application/gzip") 101 101 w.WriteHeader(http.StatusOK) 102 102 103 - if err := gitx.ArchiveTar(r.Context(), path, ref, w); err != nil { 103 + if err := repo.ArchiveTar(r.Context(), ref, w); err != nil { 104 104 slog.Error("git: archive", "ref", ref, "err", err) 105 105 return 106 106 } 路路路 110 110 w.Header().Set("content-type", "text/plain; charset=UTF-8") 111 111 w.WriteHeader(code) 112 112 fmt.Fprintf(w, "%s\n", msg) 113 -} 114 - 115 -func (h *handlers) checkRepoPublicityAndGetPath(name string, ref string) (string, error) { 116 - name = git.ResolveName(name) 117 - path, err := git.ResolvePath(h.c.Repo.Dir, name) 118 - if err != nil { 119 - return "", err 120 - } 121 - 122 - if _, oerr := git.OpenPublic(path, ref); oerr != nil { 123 - return "", oerr 124 - } 125 - 126 - return path, err 127 113 } 128 114 129 115 func (h *handlers) openPublicRepo(name, ref string) (*git.Repo, error) {
M
internal/ssh/ssh.go
路路路 9 9 "github.com/gliderlabs/ssh" 10 10 "olexsmir.xyz/mugit/internal/config" 11 11 "olexsmir.xyz/mugit/internal/git" 12 - "olexsmir.xyz/mugit/internal/git/gitx" 13 12 14 13 gossh "golang.org/x/crypto/ssh" 15 14 ) 路路路 100 99 return 101 100 } 102 101 103 - if err := gitx.UploadPack(ctx, repoPath, false, sess, sess); err != nil { 102 + if err := repo.UploadPack(ctx, false, "", sess, sess); err != nil { 104 103 s.gitError(sess, internalServerErrMsg, err) 105 104 return 106 105 } 路路路 119 118 return 120 119 } 121 120 122 - if err := gitx.UploadArchive(ctx, repoPath, sess, sess); err != nil { 121 + if err := repo.UploadArchive(ctx, sess, sess); err != nil { 123 122 s.gitError(sess, internalServerErrMsg, err) 124 123 return 125 124 } 路路路 132 131 return 133 132 } 134 133 135 - if err := gitx.ReceivePack(ctx, repoPath, sess, sess, sess.Stderr()); err != nil { 134 + if err := repo.ReceivePack(ctx, sess, sess, sess.Stderr()); err != nil { 136 135 s.gitError(sess, internalServerErrMsg, err) 137 136 return 138 137 } 路路路 179 178 180 179 func (s *Server) gitError(sess ssh.Session, msg string, err error) { 181 180 slog.Error("ssh git error", "msg", msg, "err", err) 182 - gitx.PackError(sess, msg) 183 - gitx.PackFlush(sess) 181 + git.PackError(sess, msg) 182 + git.PackFlush(sess) 184 183 sess.Exit(1) 185 184 }