this repo has no description
1package service
2
3import (
4 "bytes"
5 "fmt"
6 "io"
7 "log"
8 "net/http"
9 "os/exec"
10 "strings"
11 "sync"
12 "syscall"
13)
14
15// Mostly from charmbracelet/soft-serve and sosedoff/gitkit.
16
17type ServiceCommand struct {
18 GitProtocol string
19 Dir string
20 Stdin io.Reader
21 Stdout http.ResponseWriter
22}
23
24func (c *ServiceCommand) InfoRefs() error {
25 cmd := exec.Command("git", []string{
26 "upload-pack",
27 "--stateless-rpc",
28 "--http-backend-info-refs",
29 ".",
30 }...)
31 cmd.Env = append(cmd.Env, fmt.Sprintf("GIT_PROTOCOL=%s", c.GitProtocol))
32 cmd.Dir = c.Dir
33 cmd.SysProcAttr = &syscall.SysProcAttr{Setpgid: true}
34 stdoutPipe, _ := cmd.StdoutPipe()
35 cmd.Stderr = cmd.Stdout
36
37 if err := cmd.Start(); err != nil {
38 log.Printf("git: failed to start git-upload-pack (info/refs): %s", err)
39 return err
40 }
41
42 if !strings.Contains(c.GitProtocol, "version=2") {
43 if err := packLine(c.Stdout, "# service=git-upload-pack\n"); err != nil {
44 log.Printf("git: failed to write pack line: %s", err)
45 return err
46 }
47
48 if err := packFlush(c.Stdout); err != nil {
49 log.Printf("git: failed to flush pack: %s", err)
50 return err
51 }
52 }
53
54 buf := bytes.Buffer{}
55 if _, err := io.Copy(&buf, stdoutPipe); err != nil {
56 log.Printf("git: failed to copy stdout to tmp buffer: %s", err)
57 return err
58 }
59
60 if err := cmd.Wait(); err != nil {
61 out := strings.Builder{}
62 _, _ = io.Copy(&out, &buf)
63 log.Printf("git: failed to run git-upload-pack; err: %s; output: %s", err, out.String())
64 return err
65 }
66
67 if _, err := io.Copy(c.Stdout, &buf); err != nil {
68 log.Printf("git: failed to copy stdout: %s", err)
69 }
70
71 return nil
72}
73
74func (c *ServiceCommand) UploadPack() error {
75 var stderr bytes.Buffer
76
77 cmd := exec.Command("git", []string{
78 "-c", "uploadpack.allowFilter=true",
79 "upload-pack",
80 "--stateless-rpc",
81 ".",
82 }...)
83
84 cmd.Dir = c.Dir
85 cmd.SysProcAttr = &syscall.SysProcAttr{Setpgid: true}
86 cmd.Env = append(cmd.Env, fmt.Sprintf("GIT_PROTOCOL=%s", c.GitProtocol))
87
88 stdoutPipe, err := cmd.StdoutPipe()
89 if err != nil {
90 return fmt.Errorf("failed to create stdout pipe: %w", err)
91 }
92
93 cmd.Stderr = &stderr
94
95 stdinPipe, err := cmd.StdinPipe()
96 if err != nil {
97 return fmt.Errorf("failed to create stdin pipe: %w", err)
98 }
99
100 if err := cmd.Start(); err != nil {
101 return fmt.Errorf("failed to start git-upload-pack: %w", err)
102 }
103
104 var wg sync.WaitGroup
105
106 wg.Add(1)
107 go func() {
108 defer wg.Done()
109 defer stdinPipe.Close()
110 io.Copy(stdinPipe, c.Stdin)
111 }()
112
113 wg.Add(1)
114 go func() {
115 defer wg.Done()
116 io.Copy(newWriteFlusher(c.Stdout), stdoutPipe)
117 stdoutPipe.Close()
118 }()
119
120 wg.Wait()
121
122 if err := cmd.Wait(); err != nil {
123 return fmt.Errorf("git-upload-pack failed: %w, stderr: %s", err, stderr.String())
124 }
125
126 return nil
127}
128
129func packLine(w io.Writer, s string) error {
130 _, err := fmt.Fprintf(w, "%04x%s", len(s)+4, s)
131 return err
132}
133
134func packFlush(w io.Writer) error {
135 _, err := fmt.Fprint(w, "0000")
136 return err
137}