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 |
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 io.Writer) error {
return gitCmd("receive-pack", config{
Dir: dir,
Stdin: in,
Stdout: out,
})
}
type config struct {
Dir string
StatelessRPC bool
AllowFilter bool
ExtraArgs []string
Stdin io.Reader
Stdout 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
}
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
}
func packLine(w io.Writer, s string) error {
_, err := fmt.Fprintf(w, "%04x%s", len(s)+4, s)
return err
}
func packFlush(w io.Writer) error {
_, err := fmt.Fprint(w, "0000")
return err
}
|