// Package file implements the file transport protocol. package file import ( "bufio" "errors" "io" "os" "os/exec" "path/filepath" "strings" "gopkg.in/src-d/go-git.v4/plumbing/transport" "gopkg.in/src-d/go-git.v4/plumbing/transport/internal/common" ) // DefaultClient is the default local client. var DefaultClient = NewClient( transport.UploadPackServiceName, transport.ReceivePackServiceName, ) type runner struct { UploadPackBin string ReceivePackBin string } // NewClient returns a new local client using the given git-upload-pack and // git-receive-pack binaries. func NewClient(uploadPackBin, receivePackBin string) transport.Transport { return common.NewClient(&runner{ UploadPackBin: uploadPackBin, ReceivePackBin: receivePackBin, }) } func prefixExecPath(cmd string) (string, error) { // Use `git --exec-path` to find the exec path. execCmd := exec.Command("git", "--exec-path") stdout, err := execCmd.StdoutPipe() if err != nil { return "", err } stdoutBuf := bufio.NewReader(stdout) err = execCmd.Start() if err != nil { return "", err } execPathBytes, isPrefix, err := stdoutBuf.ReadLine() if err != nil { return "", err } if isPrefix { return "", errors.New("Couldn't read exec-path line all at once") } err = execCmd.Wait() if err != nil { return "", err } execPath := string(execPathBytes) execPath = strings.TrimSpace(execPath) cmd = filepath.Join(execPath, cmd) // Make sure it actually exists. _, err = exec.LookPath(cmd) if err != nil { return "", err } return cmd, nil } func (r *runner) Command(cmd string, ep *transport.Endpoint, auth transport.AuthMethod, ) (common.Command, error) { switch cmd { case transport.UploadPackServiceName: cmd = r.UploadPackBin case transport.ReceivePackServiceName: cmd = r.ReceivePackBin } _, err := exec.LookPath(cmd) if err != nil { if e, ok := err.(*exec.Error); ok && e.Err == exec.ErrNotFound { cmd, err = prefixExecPath(cmd) if err != nil { return nil, err } } else { return nil, err } } return &command{cmd: exec.Command(cmd, ep.Path)}, nil } type command struct { cmd *exec.Cmd stderrCloser io.Closer closed bool } func (c *command) Start() error { return c.cmd.Start() } func (c *command) StderrPipe() (io.Reader, error) { // Pipe returned by Command.StderrPipe has a race with Read + Command.Wait. // We use an io.Pipe and close it after the command finishes. r, w := io.Pipe() c.cmd.Stderr = w c.stderrCloser = r return r, nil } func (c *command) StdinPipe() (io.WriteCloser, error) { return c.cmd.StdinPipe() } func (c *command) StdoutPipe() (io.Reader, error) { return c.cmd.StdoutPipe() } func (c *command) Kill() error { c.cmd.Process.Kill() return c.Close() } // Close waits for the command to exit. func (c *command) Close() error { if c.closed { return nil } defer func() { c.closed = true _ = c.stderrCloser.Close() }() err := c.cmd.Wait() if _, ok := err.(*os.PathError); ok { return nil } // When a repository does not exist, the command exits with code 128. if _, ok := err.(*exec.ExitError); ok { return nil } return err }