diff options
author | Santiago M. Mola <santi@mola.io> | 2016-11-29 15:07:09 +0100 |
---|---|---|
committer | GitHub <noreply@github.com> | 2016-11-29 15:07:09 +0100 |
commit | 47007c70c5a696472576a522cd0e265a777f97a8 (patch) | |
tree | 2f49e52f42556a3707c24a263a571bcae39cfac6 /plumbing/transport/ssh | |
parent | 2c20b7e507a6514be2efa66143c13a60a87ee4b6 (diff) | |
download | go-git-47007c70c5a696472576a522cd0e265a777f97a8.tar.gz |
transport: add local transport (#145)
* transport: move common packp protocol out of ssh transport.
* fixtures: add fixture for empty repository.
* transport: add file:// transport
Diffstat (limited to 'plumbing/transport/ssh')
-rw-r--r-- | plumbing/transport/ssh/common.go | 139 | ||||
-rw-r--r-- | plumbing/transport/ssh/fetch_pack.go | 208 | ||||
-rw-r--r-- | plumbing/transport/ssh/fetch_pack_test.go | 1 | ||||
-rw-r--r-- | plumbing/transport/ssh/send_pack.go | 30 |
4 files changed, 48 insertions, 330 deletions
diff --git a/plumbing/transport/ssh/common.go b/plumbing/transport/ssh/common.go index c327c41..a88a328 100644 --- a/plumbing/transport/ssh/common.go +++ b/plumbing/transport/ssh/common.go @@ -3,10 +3,10 @@ package ssh import ( "errors" "fmt" - "io" "strings" "gopkg.in/src-d/go-git.v4/plumbing/transport" + "gopkg.in/src-d/go-git.v4/plumbing/transport/internal/common" "golang.org/x/crypto/ssh" ) @@ -15,155 +15,110 @@ var ( errAlreadyConnected = errors.New("ssh session already created") ) -type client struct{} - // DefaultClient is the default SSH client. -var DefaultClient = &client{} - -func (c *client) NewFetchPackSession(ep transport.Endpoint) ( - transport.FetchPackSession, error) { +var DefaultClient = common.NewClient(&runner{}) - return newFetchPackSession(ep) -} +type runner struct{} -func (c *client) NewSendPackSession(ep transport.Endpoint) ( - transport.SendPackSession, error) { +func (r *runner) Command(cmd string, ep transport.Endpoint) (common.Command, error) { + c := &command{command: cmd, endpoint: ep} + if err := c.connect(); err != nil { + return nil, err + } - return newSendPackSession(ep) + return c, nil } -type session struct { - connected bool - endpoint transport.Endpoint - client *ssh.Client - session *ssh.Session - stdin io.WriteCloser - stdout io.Reader - stderr io.Reader - sessionDone chan error - auth AuthMethod +type command struct { + *ssh.Session + connected bool + command string + endpoint transport.Endpoint + client *ssh.Client + auth AuthMethod } -func (s *session) SetAuth(auth transport.AuthMethod) error { +func (c *command) SetAuth(auth transport.AuthMethod) error { a, ok := auth.(AuthMethod) if !ok { return transport.ErrInvalidAuthMethod } - s.auth = a + c.auth = a return nil } -// Close closes the SSH session. -func (s *session) Close() error { - if !s.connected { +func (c *command) Start() error { + return c.Session.Start(endpointToCommand(c.command, c.endpoint)) +} + +// Close closes the SSH session and connection. +func (c *command) Close() error { + if !c.connected { return nil } - s.connected = false + c.connected = false //XXX: If did read the full packfile, then the session might be already // closed. - _ = s.session.Close() + _ = c.Session.Close() - return s.client.Close() + return c.client.Close() } -// ensureConnected connects to the SSH server, unless a AuthMethod was set with +// connect connects to the SSH server, unless a AuthMethod was set with // SetAuth method, by default uses an auth method based on PublicKeysCallback, // it connects to a SSH agent, using the address stored in the SSH_AUTH_SOCK // environment var. -func (s *session) connect() error { - if s.connected { +func (c *command) connect() error { + if c.connected { return errAlreadyConnected } - if err := s.setAuthFromEndpoint(); err != nil { + if err := c.setAuthFromEndpoint(); err != nil { return err } var err error - s.client, err = ssh.Dial("tcp", s.getHostWithPort(), s.auth.clientConfig()) + c.client, err = ssh.Dial("tcp", c.getHostWithPort(), c.auth.clientConfig()) if err != nil { return err } - if err := s.openSSHSession(); err != nil { - _ = s.client.Close() + c.Session, err = c.client.NewSession() + if err != nil { + _ = c.client.Close() return err } - s.connected = true + c.connected = true return nil } -func (s *session) getHostWithPort() string { - host := s.endpoint.Host - if strings.Index(s.endpoint.Host, ":") == -1 { +func (c *command) getHostWithPort() string { + host := c.endpoint.Host + if strings.Index(c.endpoint.Host, ":") == -1 { host += ":22" } return host } -func (s *session) setAuthFromEndpoint() error { +func (c *command) setAuthFromEndpoint() error { var u string - if info := s.endpoint.User; info != nil { + if info := c.endpoint.User; info != nil { u = info.Username() } var err error - s.auth, err = NewSSHAgentAuth(u) + c.auth, err = NewSSHAgentAuth(u) return err } -func (s *session) openSSHSession() error { - var err error - s.session, err = s.client.NewSession() - if err != nil { - return fmt.Errorf("cannot open SSH session: %s", err) - } - - s.stdin, err = s.session.StdinPipe() - if err != nil { - return fmt.Errorf("cannot pipe remote stdin: %s", err) - } - - s.stdout, err = s.session.StdoutPipe() - if err != nil { - return fmt.Errorf("cannot pipe remote stdout: %s", err) - } - - s.stderr, err = s.session.StderrPipe() - if err != nil { - return fmt.Errorf("cannot pipe remote stderr: %s", err) - } - - return nil -} - -func (s *session) runCommand(cmd string) chan error { - done := make(chan error) - go func() { - done <- s.session.Run(cmd) - }() - - return done -} - -const ( - githubRepoNotFoundErr = "ERROR: Repository not found." - bitbucketRepoNotFoundErr = "conq: repository does not exist." -) - -func isRepoNotFoundError(s string) bool { - if strings.HasPrefix(s, githubRepoNotFoundErr) { - return true - } - - if strings.HasPrefix(s, bitbucketRepoNotFoundErr) { - return true - } +func endpointToCommand(cmd string, ep transport.Endpoint) string { + directory := ep.Path + directory = directory[1:] - return false + return fmt.Sprintf("%s '%s'", cmd, directory) } diff --git a/plumbing/transport/ssh/fetch_pack.go b/plumbing/transport/ssh/fetch_pack.go deleted file mode 100644 index a0f52f1..0000000 --- a/plumbing/transport/ssh/fetch_pack.go +++ /dev/null @@ -1,208 +0,0 @@ -// Package ssh implements a ssh client for go-git. -package ssh - -import ( - "bufio" - "bytes" - "fmt" - "io" - - "gopkg.in/src-d/go-git.v4/plumbing/format/pktline" - "gopkg.in/src-d/go-git.v4/plumbing/protocol/packp" - "gopkg.in/src-d/go-git.v4/plumbing/transport" - "gopkg.in/src-d/go-git.v4/utils/ioutil" - - "golang.org/x/crypto/ssh" -) - -type fetchPackSession struct { - *session - cmdRun bool - advRefsRun bool - done chan error -} - -func newFetchPackSession(ep transport.Endpoint) (*fetchPackSession, error) { - s := &fetchPackSession{ - session: &session{ - endpoint: ep, - }, - } - if err := s.connect(); err != nil { - return nil, err - } - - return s, nil -} - -func (s *fetchPackSession) AdvertisedReferences() (*packp.AdvRefs, error) { - if s.advRefsRun { - return nil, transport.ErrAdvertistedReferencesAlreadyCalled - } - - defer func() { s.advRefsRun = true }() - - if err := s.ensureRunCommand(); err != nil { - return nil, err - } - - ar := packp.NewAdvRefs() - if err := ar.Decode(s.stdout); err != nil { - if err != packp.ErrEmptyAdvRefs { - return nil, err - } - - _ = s.stdin.Close() - scan := bufio.NewScanner(s.stderr) - if !scan.Scan() { - return nil, transport.ErrEmptyRemoteRepository - } - - if isRepoNotFoundError(string(scan.Bytes())) { - return nil, transport.ErrRepositoryNotFound - } - - return nil, err - } - - return ar, nil -} - -// FetchPack returns a packfile for a given upload request. -// Closing the returned reader will close the SSH session. -func (s *fetchPackSession) FetchPack(req *packp.UploadPackRequest) ( - io.ReadCloser, error) { - - if req.IsEmpty() { - return nil, transport.ErrEmptyUploadPackRequest - } - - if !s.advRefsRun { - if _, err := s.AdvertisedReferences(); err != nil { - return nil, err - } - } - - if err := fetchPack(s.stdin, s.stdout, req); err != nil { - return nil, err - } - - fs := &fetchSession{ - Reader: s.stdout, - session: s.session.session, - done: s.done, - } - - r, err := ioutil.NonEmptyReader(fs) - if err == ioutil.ErrEmptyReader { - _ = fs.Close() - return nil, transport.ErrEmptyUploadPackRequest - } - - return ioutil.NewReadCloser(r, fs), nil -} - -func (s *fetchPackSession) ensureRunCommand() error { - if s.cmdRun { - return nil - } - - s.cmdRun = true - s.done = s.runCommand(s.getCommand()) - return nil -} - -type fetchSession struct { - io.Reader - session *ssh.Session - done <-chan error -} - -// Close closes the session and collects the output state of the remote -// SSH command. -// -// If both the remote command and the closing of the session completes -// susccessfully it returns nil. -// -// If the remote command completes unsuccessfully or is interrupted by a -// signal, it returns the corresponding *ExitError. -// -// Otherwise, if clossing the SSH session fails it returns the close -// error. Closing the session when the other has already close it is -// not cosidered an error. -func (f *fetchSession) Close() (err error) { - if err := <-f.done; err != nil { - return err - } - - if err := f.session.Close(); err != nil && err != io.EOF { - return err - } - - return nil -} - -func (s *fetchPackSession) getCommand() string { - directory := s.endpoint.Path - directory = directory[1:] - - return fmt.Sprintf("git-upload-pack '%s'", directory) -} - -var ( - nak = []byte("NAK") - eol = []byte("\n") -) - -// FetchPack implements the git-fetch-pack protocol. -// -// TODO support multi_ack mode -// TODO support multi_ack_detailed mode -// TODO support acks for common objects -// TODO build a proper state machine for all these processing options -func fetchPack(w io.WriteCloser, r io.Reader, - req *packp.UploadPackRequest) error { - - if err := req.UploadRequest.Encode(w); err != nil { - return fmt.Errorf("sending upload-req message: %s", err) - } - - if err := req.UploadHaves.Encode(w); err != nil { - return fmt.Errorf("sending haves message: %s", err) - } - - if err := sendDone(w); err != nil { - return fmt.Errorf("sending done message: %s", err) - } - - if err := w.Close(); err != nil { - return fmt.Errorf("closing input: %s", err) - } - - if err := readNAK(r); err != nil { - return fmt.Errorf("reading NAK: %s", err) - } - - return nil -} - -func sendDone(w io.Writer) error { - e := pktline.NewEncoder(w) - - return e.Encodef("done\n") -} - -func readNAK(r io.Reader) error { - s := pktline.NewScanner(r) - if !s.Scan() { - return s.Err() - } - - b := s.Bytes() - b = bytes.TrimSuffix(b, eol) - if !bytes.Equal(b, nak) { - return fmt.Errorf("expecting NAK, found %q instead", string(b)) - } - - return nil -} diff --git a/plumbing/transport/ssh/fetch_pack_test.go b/plumbing/transport/ssh/fetch_pack_test.go index a0321b3..927e9a8 100644 --- a/plumbing/transport/ssh/fetch_pack_test.go +++ b/plumbing/transport/ssh/fetch_pack_test.go @@ -33,4 +33,5 @@ func (s *FetchPackSuite) SetUpSuite(c *C) { ep, err = transport.NewEndpoint("git@github.com:git-fixtures/non-existent.git") c.Assert(err, IsNil) s.FetchPackSuite.NonExistentEndpoint = ep + } diff --git a/plumbing/transport/ssh/send_pack.go b/plumbing/transport/ssh/send_pack.go deleted file mode 100644 index adf67bb..0000000 --- a/plumbing/transport/ssh/send_pack.go +++ /dev/null @@ -1,30 +0,0 @@ -package ssh - -import ( - "errors" - "io" - - "gopkg.in/src-d/go-git.v4/plumbing/protocol/packp" - "gopkg.in/src-d/go-git.v4/plumbing/transport" -) - -var errSendPackNotSupported = errors.New("send-pack not supported yet") - -type sendPackSession struct { - *session -} - -func newSendPackSession(ep transport.Endpoint) (transport.SendPackSession, - error) { - - return &sendPackSession{&session{}}, nil -} - -func (s *sendPackSession) AdvertisedReferences() (*packp.AdvRefs, error) { - - return nil, errSendPackNotSupported -} - -func (s *sendPackSession) SendPack() (io.WriteCloser, error) { - return nil, errSendPackNotSupported -} |