package git import ( "bytes" "context" "errors" "fmt" "io" "os" "os/exec" "strings" "time" ) var ErrUnsupportedService = errors.New("unsupported git service") func subcommandForService(service string) (string, error) { switch service { case "git-upload-pack": return "upload-pack", nil case "git-receive-pack": return "receive-pack", nil default: return "", ErrUnsupportedService } } func pktLine(payload string) string { return fmt.Sprintf("%04x%s", len(payload)+4, payload) } func AdvertiseRefs(ctx context.Context, repoPath, service string, out io.Writer) error { subcmd, err := subcommandForService(service) if err != nil { return err } cctx, cancel := context.WithTimeout(ctx, 30*time.Second) defer cancel() var stdout, stderr bytes.Buffer cmd := exec.CommandContext(cctx, "git", subcmd, "--stateless-rpc", "--advertise-refs", repoPath) cmd.Env = append(os.Environ(), "GIT_TERMINAL_PROMPT=0", "LC_ALL=C") cmd.Stdout = &stdout cmd.Stderr = &stderr if err := cmd.Run(); err != nil { return fmt.Errorf("git %s advertise-refs: %w (%s)", subcmd, err, strings.TrimSpace(stderr.String())) } header := pktLine(fmt.Sprintf("# service=%s\n", service)) + "0000" if _, err := io.WriteString(out, header); err != nil { return err } _, err = out.Write(stdout.Bytes()) return err } func ServiceRPC(ctx context.Context, repoPath, service string, in io.Reader, out io.Writer) error { subcmd, err := subcommandForService(service) if err != nil { return err } cctx, cancel := context.WithTimeout(ctx, 2*time.Minute) defer cancel() var stderr bytes.Buffer cmd := exec.CommandContext(cctx, "git", subcmd, "--stateless-rpc", repoPath) cmd.Env = append(os.Environ(), "GIT_TERMINAL_PROMPT=0", "LC_ALL=C") cmd.Stdin = in cmd.Stdout = out cmd.Stderr = &stderr if err := cmd.Run(); err != nil { return fmt.Errorf("git %s stateless-rpc: %w (%s)", subcmd, err, strings.TrimSpace(stderr.String())) } if service == "git-receive-pack" { _, _ = EnsureDefaultBranch(ctx, repoPath, "main") _, _ = runGit(ctx, repoPath, 5*time.Second, "update-server-info") } return nil }