all repos

mugit @ a00560c

馃惍 git server that your cow will love
2 files changed, 86 insertions(+), 32 deletions(-)
refactor git
Author: Oleksandr Smirnov olexsmir@gmail.com
Committed at: 2026-01-21 01:49:14 +0200
Authored at: 2026-01-21 00:46:05 +0200
Change ID: wkuwunxpuylrrwmzwrvmltsxtkszkrnt
Parent: 70e0353
M internal/git/gitservice/gitservice.go
路路路
        5
        5
         	"fmt"

      
        6
        6
         	"io"

      
        7
        7
         	"os/exec"

      
        8
        
        -	"strings"

      
        
        8
        +	"sync"

      
        9
        9
         	"syscall"

      
        10
        10
         )

      
        11
        
        -

      
        12
        
        -// Thanks https://git.icyphox.sh/legit/blob/master/git/service/service.go

      
        13
        11
         

      
        14
        12
         // InfoRefs executes git-upload-pack --advertise-refs for smart-HTTP discovery.

      
        15
        13
         func InfoRefs(dir string, out io.Writer) error {

      路路路
        20
        18
         		".")

      
        21
        19
         	cmd.Dir = dir

      
        22
        20
         	cmd.SysProcAttr = &syscall.SysProcAttr{Setpgid: true}

      
        23
        
        -	stdoutPipe, _ := cmd.StdoutPipe()

      
        
        21
        +

      
        
        22
        +	stdout, err := cmd.StdoutPipe()

      
        
        23
        +	if err != nil {

      
        
        24
        +		return fmt.Errorf("stdout pipe: %w", err)

      
        
        25
        +	}

      
        24
        26
         	cmd.Stderr = cmd.Stdout

      
        25
        
        -	defer stdoutPipe.Close()

      
        26
        27
         

      
        27
        28
         	if err := cmd.Start(); err != nil {

      
        28
        29
         		return fmt.Errorf("start git-upload-pack: %w", err)

      路路路
        31
        32
         	if err := packLine(out, "# service=git-upload-pack\n"); err != nil {

      
        32
        33
         		return fmt.Errorf("write pack line: %w", err)

      
        33
        34
         	}

      
        34
        
        -

      
        35
        35
         	if err := packFlush(out); err != nil {

      
        36
        36
         		return fmt.Errorf("flush pack: %w", err)

      
        37
        37
         	}

      
        38
        38
         

      
        39
        39
         	var buf bytes.Buffer

      
        40
        
        -	if _, err := io.Copy(&buf, stdoutPipe); err != nil {

      
        
        40
        +	if _, err := io.Copy(&buf, stdout); err != nil {

      
        41
        41
         		return fmt.Errorf("copy stdout to buffer: %w", err)

      
        42
        42
         	}

      
        43
        43
         

      
        44
        44
         	if err := cmd.Wait(); err != nil {

      
        45
        
        -		var out strings.Builder

      
        46
        
        -		io.Copy(&out, &buf)

      
        47
        
        -		return fmt.Errorf("git-upload-pack: %w: %s", err, out.String())

      
        
        45
        +		return fmt.Errorf("git-upload-pack: %w", err)

      
        48
        46
         	}

      
        49
        47
         

      
        50
        48
         	if _, err := io.Copy(out, &buf); err != nil {

      路路路
        55
        53
         }

      
        56
        54
         

      
        57
        55
         // UploadPack executes git-upload-pack for smart-HTTP git fetch/clone.

      
        58
        
        -func UploadPack(dir string, in io.Reader, out io.Writer) error {

      
        59
        
        -	cmd := exec.Command("git",

      
        60
        
        -		"-c", "uploadpack.allowFilter=true",

      
        61
        
        -		"upload-pack",

      
        62
        
        -		"--stateless-rpc",

      
        63
        
        -		".")

      
        64
        
        -	cmd.Dir = dir

      
        
        56
        +// StatelessRPC should be true in case it's used over http, and false for ssh.

      
        
        57
        +func UploadPack(dir string, statelessRPC bool, in io.Reader, out io.Writer) error {

      
        
        58
        +	return gitCmd("upload-pack", config{

      
        
        59
        +		Dir:          dir,

      
        
        60
        +		StatelessRPC: statelessRPC,

      
        
        61
        +		AllowFilter:  true,

      
        
        62
        +		Stdin:        in,

      
        
        63
        +		Stdout:       out,

      
        
        64
        +	})

      
        
        65
        +}

      
        
        66
        +

      
        
        67
        +func ReceivePack(dir string, in io.Reader, out io.Writer) error {

      
        
        68
        +	return gitCmd("receive-pack", config{

      
        
        69
        +		Dir:    dir,

      
        
        70
        +		Stdin:  in,

      
        
        71
        +		Stdout: out,

      
        
        72
        +	})

      
        
        73
        +}

      
        
        74
        +

      
        
        75
        +type config struct {

      
        
        76
        +	Dir          string

      
        
        77
        +	StatelessRPC bool

      
        
        78
        +	AllowFilter  bool

      
        
        79
        +	ExtraArgs    []string

      
        
        80
        +	Stdin        io.Reader

      
        
        81
        +	Stdout       io.Writer

      
        
        82
        +}

      
        
        83
        +

      
        
        84
        +func gitCmd(service string, c config) error {

      
        
        85
        +	args := []string{}

      
        
        86
        +	if c.AllowFilter {

      
        
        87
        +		args = append(args, "-c", "uploadpack.allowFilter=true")

      
        
        88
        +	}

      
        
        89
        +

      
        
        90
        +	args = append(args, service)

      
        
        91
        +	if c.StatelessRPC {

      
        
        92
        +		args = append(args, "--stateless-rpc")

      
        
        93
        +	}

      
        
        94
        +

      
        
        95
        +	args = append(args, c.ExtraArgs...)

      
        
        96
        +	args = append(args, ".")

      
        
        97
        +

      
        
        98
        +	cmd := exec.Command("git", args...)

      
        
        99
        +	cmd.Dir = c.Dir

      
        65
        100
         	cmd.SysProcAttr = &syscall.SysProcAttr{Setpgid: true}

      
        66
        101
         

      
        67
        
        -	stdoutPipe, _ := cmd.StdoutPipe()

      
        68
        
        -	cmd.Stderr = cmd.Stdout

      
        69
        
        -	defer stdoutPipe.Close()

      
        
        102
        +	var (

      
        
        103
        +		err   error

      
        
        104
        +		stdin io.WriteCloser

      
        
        105
        +	)

      
        
        106
        +

      
        
        107
        +	if c.Stdin != nil {

      
        
        108
        +		stdin, err = cmd.StdinPipe()

      
        
        109
        +		if err != nil {

      
        
        110
        +			return err

      
        
        111
        +		}

      
        
        112
        +	}

      
        70
        113
         

      
        71
        
        -	stdinPipe, err := cmd.StdinPipe()

      
        
        114
        +	stdout, err := cmd.StdoutPipe()

      
        72
        115
         	if err != nil {

      
        73
        
        -		return fmt.Errorf("stdin pipe: %w", err)

      
        
        116
        +		return err

      
        74
        117
         	}

      
        75
        
        -	defer stdinPipe.Close()

      
        
        118
        +	cmd.Stderr = cmd.Stdout

      
        76
        119
         

      
        77
        120
         	if err := cmd.Start(); err != nil {

      
        78
        
        -		return fmt.Errorf("start git-upload-pack: %w", err)

      
        
        121
        +		return fmt.Errorf("start %s: %w", service, err)

      
        79
        122
         	}

      
        80
        123
         

      
        81
        
        -	if _, err := io.Copy(stdinPipe, in); err != nil {

      
        82
        
        -		return fmt.Errorf("copy to stdin: %w", err)

      
        
        124
        +	if c.Stdin != nil {

      
        
        125
        +		// Don't add to WaitGroup - stdin closes when client closes,

      
        
        126
        +		// shouldn't block waiting for output to finish

      
        
        127
        +		go func() {

      
        
        128
        +			defer stdin.Close()

      
        
        129
        +			io.Copy(stdin, c.Stdin)

      
        
        130
        +		}()

      
        83
        131
         	}

      
        84
        
        -	stdinPipe.Close()

      
        
        132
        +

      
        
        133
        +	var wg sync.WaitGroup

      
        
        134
        +	var stdoutErr error

      
        
        135
        +

      
        
        136
        +	wg.Go(func() {

      
        
        137
        +		_, stdoutErr = io.Copy(c.Stdout, stdout)

      
        
        138
        +	})

      
        85
        139
         

      
        86
        
        -	if _, err := io.Copy(out, stdoutPipe); err != nil {

      
        87
        
        -		return fmt.Errorf("copy stdout: %w", err)

      
        
        140
        +	wg.Wait()

      
        
        141
        +

      
        
        142
        +	if stdoutErr != nil {

      
        
        143
        +		return fmt.Errorf("copy stdout: %w", stdoutErr)

      
        88
        144
         	}

      
        89
        145
         

      
        90
        146
         	if err := cmd.Wait(); err != nil {

      
        91
        
        -		return fmt.Errorf("git-upload-pack: %w", err)

      
        
        147
        +		return fmt.Errorf("%s: %w", service, err)

      
        92
        148
         	}

      
        93
        
        -

      
        94
        149
         	return nil

      
        95
        150
         }

      
        96
        151
         

      
M internal/handlers/git.go
路路路
        20
        20
         

      
        21
        21
         	if err := gitservice.InfoRefs(

      
        22
        22
         		filepath.Join(h.c.Repo.Dir, name), // FIXME: use securejoin

      
        23
        
        -		true,

      
        24
        23
         		w,

      
        25
        24
         	); err != nil {

      
        26
        25
         		slog.Error("git: info/refs", "err", err)