2 files changed,
86 insertions(+),
32 deletions(-)
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