aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorSantiago M. Mola <santi@mola.io>2016-11-29 15:07:09 +0100
committerGitHub <noreply@github.com>2016-11-29 15:07:09 +0100
commit47007c70c5a696472576a522cd0e265a777f97a8 (patch)
tree2f49e52f42556a3707c24a263a571bcae39cfac6
parent2c20b7e507a6514be2efa66143c13a60a87ee4b6 (diff)
downloadgo-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
-rw-r--r--fixtures/fixtures.go5
-rw-r--r--plumbing/transport/client/client.go5
-rw-r--r--plumbing/transport/client/client_test.go2
-rw-r--r--plumbing/transport/common.go3
-rw-r--r--plumbing/transport/file/common.go72
-rw-r--r--plumbing/transport/file/common_test.go9
-rw-r--r--plumbing/transport/file/fetch_pack_test.go48
-rw-r--r--plumbing/transport/internal/common/common.go295
-rw-r--r--plumbing/transport/ssh/common.go139
-rw-r--r--plumbing/transport/ssh/fetch_pack.go208
-rw-r--r--plumbing/transport/ssh/fetch_pack_test.go1
-rw-r--r--plumbing/transport/ssh/send_pack.go30
12 files changed, 484 insertions, 333 deletions
diff --git a/fixtures/fixtures.go b/fixtures/fixtures.go
index 324f2da..4e5e2bb 100644
--- a/fixtures/fixtures.go
+++ b/fixtures/fixtures.go
@@ -115,6 +115,11 @@ var fixtures = Fixtures{{
Tags: []string{"packfile", "diff-tree"},
URL: "https://github.com/toqueteos/ts3.git",
PackfileHash: plumbing.NewHash("21b33a26eb7ffbd35261149fe5d886b9debab7cb"),
+}, {
+ Tags: []string{"empty", ".git"},
+ URL: "https://github.com/git-fixtures/empty.git",
+ DotGitHash: plumbing.NewHash("4abe340d8d378baf7c2bfb2854c0fa498642bac3"),
+ ObjectsCount: 0,
}}
func All() Fixtures {
diff --git a/plumbing/transport/client/client.go b/plumbing/transport/client/client.go
index 5c6da05..770b7dc 100644
--- a/plumbing/transport/client/client.go
+++ b/plumbing/transport/client/client.go
@@ -4,6 +4,7 @@ import (
"fmt"
"gopkg.in/src-d/go-git.v4/plumbing/transport"
+ "gopkg.in/src-d/go-git.v4/plumbing/transport/file"
"gopkg.in/src-d/go-git.v4/plumbing/transport/http"
"gopkg.in/src-d/go-git.v4/plumbing/transport/ssh"
)
@@ -13,6 +14,7 @@ var Protocols = map[string]transport.Client{
"http": http.DefaultClient,
"https": http.DefaultClient,
"ssh": ssh.DefaultClient,
+ "file": file.DefaultClient,
}
// InstallProtocol adds or modifies an existing protocol.
@@ -21,7 +23,8 @@ func InstallProtocol(scheme string, c transport.Client) {
}
// NewClient returns the appropriate client among of the set of known protocols:
-// HTTP, SSH. See `InstallProtocol` to add or modify protocols.
+// http://, https://, ssh:// and file://.
+// See `InstallProtocol` to add or modify protocols.
func NewClient(endpoint transport.Endpoint) (transport.Client, error) {
f, ok := Protocols[endpoint.Scheme]
if !ok {
diff --git a/plumbing/transport/client/client_test.go b/plumbing/transport/client/client_test.go
index 0f4b2e5..a0c8208 100644
--- a/plumbing/transport/client/client_test.go
+++ b/plumbing/transport/client/client_test.go
@@ -38,7 +38,7 @@ func (s *ClientSuite) TestNewClientSSH(c *C) {
output, err := NewClient(e)
c.Assert(err, IsNil)
- c.Assert(typeAsString(output), Equals, "*ssh.client")
+ c.Assert(output, NotNil)
}
func (s *ClientSuite) TestNewClientUnknown(c *C) {
diff --git a/plumbing/transport/common.go b/plumbing/transport/common.go
index 2f6fcee..9715a39 100644
--- a/plumbing/transport/common.go
+++ b/plumbing/transport/common.go
@@ -34,7 +34,8 @@ var (
)
const (
- UploadPackServiceName = "git-upload-pack"
+ UploadPackServiceName = "git-upload-pack"
+ ReceivePackServiceName = "git-receive-pack"
)
// Client can initiate git-fetch-pack and git-send-pack processes.
diff --git a/plumbing/transport/file/common.go b/plumbing/transport/file/common.go
new file mode 100644
index 0000000..82cbba2
--- /dev/null
+++ b/plumbing/transport/file/common.go
@@ -0,0 +1,72 @@
+package file
+
+import (
+ "io"
+ "os/exec"
+
+ "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.Client {
+ return common.NewClient(&runner{
+ UploadPackBin: uploadPackBin,
+ ReceivePackBin: receivePackBin,
+ })
+}
+
+func (r *runner) Command(cmd string, ep transport.Endpoint) (common.Command, error) {
+ return &command{cmd: exec.Command(cmd, ep.Path)}, nil
+}
+
+type command struct {
+ cmd *exec.Cmd
+ closed bool
+}
+
+func (c *command) SetAuth(auth transport.AuthMethod) error {
+ if auth != nil {
+ return transport.ErrInvalidAuthMethod
+ }
+
+ return nil
+}
+
+func (c *command) Start() error {
+ return c.cmd.Start()
+}
+
+func (c *command) StderrPipe() (io.Reader, error) {
+ return c.cmd.StderrPipe()
+}
+
+func (c *command) StdinPipe() (io.WriteCloser, error) {
+ return c.cmd.StdinPipe()
+}
+
+func (c *command) StdoutPipe() (io.Reader, error) {
+ return c.cmd.StdoutPipe()
+}
+
+// Close waits for the command to exit.
+func (c *command) Close() error {
+ return c.cmd.Process.Kill()
+}
+
+func (c *command) Wait() error {
+ defer func() { c.closed = true }()
+ return c.cmd.Wait()
+}
diff --git a/plumbing/transport/file/common_test.go b/plumbing/transport/file/common_test.go
new file mode 100644
index 0000000..94ca4c9
--- /dev/null
+++ b/plumbing/transport/file/common_test.go
@@ -0,0 +1,9 @@
+package file
+
+import (
+ "testing"
+
+ . "gopkg.in/check.v1"
+)
+
+func Test(t *testing.T) { TestingT(t) }
diff --git a/plumbing/transport/file/fetch_pack_test.go b/plumbing/transport/file/fetch_pack_test.go
new file mode 100644
index 0000000..80f11ee
--- /dev/null
+++ b/plumbing/transport/file/fetch_pack_test.go
@@ -0,0 +1,48 @@
+package file
+
+import (
+ "fmt"
+ "os/exec"
+
+ "gopkg.in/src-d/go-git.v4/fixtures"
+ "gopkg.in/src-d/go-git.v4/plumbing/transport"
+ "gopkg.in/src-d/go-git.v4/plumbing/transport/test"
+
+ . "gopkg.in/check.v1"
+)
+
+type FetchPackSuite struct {
+ fixtures.Suite
+ test.FetchPackSuite
+}
+
+var _ = Suite(&FetchPackSuite{})
+
+func (s *FetchPackSuite) SetUpSuite(c *C) {
+ s.Suite.SetUpSuite(c)
+
+ if err := exec.Command("git", "--version").Run(); err != nil {
+ c.Skip("git command not found")
+ }
+
+ s.FetchPackSuite.Client = DefaultClient
+
+ fixture := fixtures.Basic().One()
+ path := fixture.DotGit().Base()
+ url := fmt.Sprintf("file://%s", path)
+ ep, err := transport.NewEndpoint(url)
+ c.Assert(err, IsNil)
+ s.Endpoint = ep
+
+ fixture = fixtures.ByTag("empty").One()
+ path = fixture.DotGit().Base()
+ url = fmt.Sprintf("file://%s", path)
+ ep, err = transport.NewEndpoint(url)
+ c.Assert(err, IsNil)
+ s.EmptyEndpoint = ep
+
+ url = fmt.Sprintf("file://%s/%s", fixtures.DataFolder, "non-existent")
+ ep, err = transport.NewEndpoint(url)
+ c.Assert(err, IsNil)
+ s.NonExistentEndpoint = ep
+}
diff --git a/plumbing/transport/internal/common/common.go b/plumbing/transport/internal/common/common.go
new file mode 100644
index 0000000..10e395e
--- /dev/null
+++ b/plumbing/transport/internal/common/common.go
@@ -0,0 +1,295 @@
+// Package common implements the git pack protocol with a pluggable transport.
+// This is a low-level package to implement new transports. Use a concrete
+// implementation instead (e.g. http, file, ssh).
+//
+// A simple example of usage can be found in the file package.
+package common
+
+import (
+ "bufio"
+ "bytes"
+ "errors"
+ "fmt"
+ "io"
+ "strings"
+
+ "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"
+)
+
+// Commander creates Command instances. This is the main entry point for
+// transport implementations.
+type Commander interface {
+ // Command creates a new Command for the given git command and
+ // endpoint. cmd can be git-upload-pack or git-receive-pack. An
+ // error should be returned if the endpoint is not supported or the
+ // command cannot be created (e.g. binary does not exist, connection
+ // cannot be established).
+ Command(cmd string, ep transport.Endpoint) (Command, error)
+}
+
+// Command is used for a single command execution.
+// This interface is modeled after exec.Cmd and ssh.Session in the standard
+// library.
+type Command interface {
+ // SetAuth sets the authentication method.
+ SetAuth(transport.AuthMethod) error
+ // StderrPipe returns a pipe that will be connected to the command's
+ // standard error when the command starts. It should not be called after
+ // Start.
+ StderrPipe() (io.Reader, error)
+ // StdinPipe returns a pipe that will be connected to the command's
+ // standard input when the command starts. It should not be called after
+ // Start. The pipe should be closed when no more input is expected.
+ StdinPipe() (io.WriteCloser, error)
+ // StdoutPipe returns a pipe that will be connected to the command's
+ // standard output when the command starts. It should not be called after
+ // Start.
+ StdoutPipe() (io.Reader, error)
+ // Start starts the specified command. It does not wait for it to
+ // complete.
+ Start() error
+ // Wait waits for the command to exit. It must have been started by
+ // Start. The returned error is nil if the command runs, has no
+ // problems copying stdin, stdout, and stderr, and exits with a zero
+ // exit status.
+ Wait() error
+ // Close closes the command without waiting for it to exit and releases
+ // any resources. It can be called to forcibly finish the command
+ // without calling to Wait or to release resources after calling
+ // Wait.
+ Close() error
+}
+
+type client struct {
+ cmdr Commander
+}
+
+// NewClient creates a new client using the given Commander.
+func NewClient(runner Commander) transport.Client {
+ return &client{runner}
+}
+
+// NewFetchPackSession creates a new FetchPackSession.
+func (c *client) NewFetchPackSession(ep transport.Endpoint) (
+ transport.FetchPackSession, error) {
+
+ return c.newSession(transport.UploadPackServiceName, ep)
+}
+
+// NewSendPackSession creates a new SendPackSession.
+func (c *client) NewSendPackSession(ep transport.Endpoint) (
+ transport.SendPackSession, error) {
+
+ return nil, errors.New("git send-pack not supported")
+}
+
+type session struct {
+ Stdin io.WriteCloser
+ Stdout io.Reader
+ Stderr io.Reader
+ Command Command
+
+ advRefsRun bool
+}
+
+func (c *client) newSession(s string, ep transport.Endpoint) (*session, error) {
+ cmd, err := c.cmdr.Command(s, ep)
+ if err != nil {
+ return nil, err
+ }
+
+ stdin, err := cmd.StdinPipe()
+ if err != nil {
+ return nil, err
+ }
+
+ stdout, err := cmd.StdoutPipe()
+ if err != nil {
+ return nil, err
+ }
+
+ stderr, err := cmd.StderrPipe()
+ if err != nil {
+ return nil, err
+ }
+
+ if err := cmd.Start(); err != nil {
+ return nil, err
+ }
+
+ return &session{
+ Stdin: stdin,
+ Stdout: stdout,
+ Stderr: stderr,
+ Command: cmd,
+ }, nil
+}
+
+// SetAuth delegates to the command's SetAuth.
+func (s *session) SetAuth(auth transport.AuthMethod) error {
+ return s.Command.SetAuth(auth)
+}
+
+// AdvertisedReferences retrieves the advertised references from the server.
+func (s *session) AdvertisedReferences() (*packp.AdvRefs, error) {
+ if s.advRefsRun {
+ return nil, transport.ErrAdvertistedReferencesAlreadyCalled
+ }
+
+ defer func() { s.advRefsRun = true }()
+
+ ar := packp.NewAdvRefs()
+ if err := ar.Decode(s.Stdout); err != nil {
+ if err != packp.ErrEmptyAdvRefs {
+ return nil, err
+ }
+
+ _ = s.Stdin.Close()
+ err = transport.ErrEmptyRemoteRepository
+
+ 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 performs a request to the server to fetch a packfile. A reader is
+// returned with the packfile content. The reader must be closed after reading.
+func (s *session) 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
+ }
+
+ r, err := ioutil.NonEmptyReader(s.Stdout)
+ if err == ioutil.ErrEmptyReader {
+ if c, ok := s.Stdout.(io.Closer); ok {
+ _ = c.Close()
+ }
+
+ return nil, transport.ErrEmptyUploadPackRequest
+ }
+
+ if err != nil {
+ return nil, err
+ }
+
+ wc := &waitCloser{s.Command}
+ rc := ioutil.NewReadCloser(r, wc)
+
+ return rc, nil
+}
+
+func (s *session) Close() error {
+ return s.Command.Close()
+}
+
+const (
+ githubRepoNotFoundErr = "ERROR: Repository not found."
+ bitbucketRepoNotFoundErr = "conq: repository does not exist."
+ localRepoNotFoundErr = "does not appear to be a git repository"
+)
+
+func isRepoNotFoundError(s string) bool {
+ if strings.HasPrefix(s, githubRepoNotFoundErr) {
+ return true
+ }
+
+ if strings.HasPrefix(s, bitbucketRepoNotFoundErr) {
+ return true
+ }
+
+ if strings.HasSuffix(s, localRepoNotFoundErr) {
+ return true
+ }
+
+ return false
+}
+
+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
+}
+
+type waitCloser struct {
+ Command Command
+}
+
+// Close waits until the command exits and returns error, if any.
+func (c *waitCloser) Close() error {
+ return c.Command.Wait()
+}
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
-}