all repos

mugit @ 10fc8ef

🐮 git server that your cow will love

mugit/internal/git/gitservice/gitservice.go(view raw)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
package gitservice

import (
	"bytes"
	"fmt"
	"io"
	"os/exec"
	"sync"
	"syscall"
)

// InfoRefs executes git-upload-pack --advertise-refs for smart-HTTP discovery.
func InfoRefs(dir string, out io.Writer) error {
	cmd := exec.Command("git",
		"upload-pack",
		"--stateless-rpc",
		"--advertise-refs",
		".")
	cmd.Dir = dir
	cmd.SysProcAttr = &syscall.SysProcAttr{Setpgid: true}

	stdout, err := cmd.StdoutPipe()
	if err != nil {
		return fmt.Errorf("stdout pipe: %w", err)
	}
	cmd.Stderr = cmd.Stdout

	if err := cmd.Start(); err != nil {
		return fmt.Errorf("start git-upload-pack: %w", err)
	}

	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)
	}

	var buf bytes.Buffer
	if _, err := io.Copy(&buf, stdout); err != nil {
		return fmt.Errorf("copy stdout to buffer: %w", err)
	}

	if err := cmd.Wait(); err != nil {
		return fmt.Errorf("git-upload-pack: %w", err)
	}

	if _, err := io.Copy(out, &buf); err != nil {
		return fmt.Errorf("copy buffer to output: %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(dir string, statelessRPC bool, in io.Reader, out io.Writer) error {
	return gitCmd("upload-pack", config{
		Dir:          dir,
		StatelessRPC: statelessRPC,
		AllowFilter:  true,
		Stdin:        in,
		Stdout:       out,
	})
}

func ReceivePack(dir string, in io.Reader, out, stderr io.Writer) error {
	return gitCmd("receive-pack", config{
		Dir:    dir,
		Stdin:  in,
		Stdout: out,
		Stderr: stderr,
	})
}

type config struct {
	Dir          string
	StatelessRPC bool
	AllowFilter  bool
	ExtraArgs    []string
	Stdin        io.Reader
	Stdout       io.Writer
	Stderr       io.Writer
}

func gitCmd(service string, c config) error {
	args := []string{}
	if c.AllowFilter {
		args = append(args, "-c", "uploadpack.allowFilter=true")
	}

	args = append(args, service)
	if c.StatelessRPC {
		args = append(args, "--stateless-rpc")
	}

	args = append(args, c.ExtraArgs...)
	args = append(args, ".")

	cmd := exec.Command("git", args...)
	cmd.Dir = c.Dir
	cmd.SysProcAttr = &syscall.SysProcAttr{Setpgid: true}

	var (
		err   error
		stdin io.WriteCloser
	)

	if c.Stdin != nil {
		stdin, err = cmd.StdinPipe()
		if err != nil {
			return err
		}
	}

	stdout, err := cmd.StdoutPipe()
	if err != nil {
		return err
	}
	if c.Stderr != nil {
		cmd.Stderr = c.Stderr
	} else {
		cmd.Stderr = cmd.Stdout
	}

	if err := cmd.Start(); err != nil {
		return fmt.Errorf("start %s: %w", service, err)
	}

	if c.Stdin != nil {
		// Don't add to WaitGroup - stdin closes when client closes,
		// shouldn't block waiting for output to finish
		go func() {
			defer stdin.Close()
			io.Copy(stdin, c.Stdin)
		}()
	}

	var wg sync.WaitGroup
	var stdoutErr error

	wg.Go(func() {
		_, stdoutErr = io.Copy(c.Stdout, stdout)
	})

	wg.Wait()

	if stdoutErr != nil {
		return fmt.Errorf("copy stdout: %w", stdoutErr)
	}

	if err := cmd.Wait(); err != nil {
		return fmt.Errorf("%s: %w", service, 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
}

// PackSideband writes a message to sideband channel (displays as "remote: <msg>" in git client).
// Channel 2 = progress/info, Channel 3 = error.
func PackSideband(w io.Writer, channel byte, msg string) error {
	return PackLine(w, string(channel)+msg)
}

// 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)
}