all repos

mugit @ f421981544afb124582785961edf224bd7117b3a

馃惍 git server that your cow will love
7 files changed, 105 insertions(+), 124 deletions(-)
git: move gitx into git
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.gointernal/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.gointernal/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
         }