aboutsummaryrefslogtreecommitdiffstats
path: root/plumbing/transport/git/common.go
blob: a68b7df77f7516120b00852dec15ed3a12be225e (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
package git

import (
	"errors"
	"fmt"
	"io"
	"net"
	"strings"
	"time"

	"gopkg.in/src-d/go-git.v4/plumbing/format/pktline"
	"gopkg.in/src-d/go-git.v4/plumbing/transport"
	"gopkg.in/src-d/go-git.v4/plumbing/transport/internal/common"
	"gopkg.in/src-d/go-git.v4/utils/ioutil"
)

var (
	errAlreadyConnected = errors.New("tcp connection already connected")
)

// DefaultClient is the default git client.
var DefaultClient = common.NewClient(&runner{})

type runner struct{}

// Command returns a new Command for the given cmd in the given Endpoint
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 c, nil
}

type command struct {
	conn      net.Conn
	connected bool
	command   string
	endpoint  transport.Endpoint
}

// SetAuth cannot be called since git protocol doesn't support authentication
func (c *command) SetAuth(auth transport.AuthMethod) error {
	return transport.ErrInvalidAuthMethod
}

// Start executes the command sending the required message to the TCP connection
func (c *command) Start() error {
	cmd := endpointToCommand(c.command, c.endpoint)
	line, err := pktline.EncodeFromString(cmd)
	if err != nil {
		return err
	}

	_, err = c.conn.Write([]byte(line))
	return err
}

func (c *command) connect() error {
	if c.connected {
		return errAlreadyConnected
	}

	var err error
	c.conn, err = net.DialTimeout("tcp", c.getHostWithPort(), time.Second)
	if err != nil {
		return err
	}

	c.connected = true
	return nil
}

func (c *command) getHostWithPort() string {
	host := c.endpoint.Host
	if strings.Index(c.endpoint.Host, ":") == -1 {
		host += ":9418"
	}

	return host
}

// StderrPipe git protocol doesn't have any dedicated error channel
func (c *command) StderrPipe() (io.Reader, error) {
	return nil, nil
}

// StdinPipe return the underlying connection as WriteCloser, wrapped to prevent
// call to the Close function from the connection, a command execution in git
// protocol can't be closed or killed
func (c *command) StdinPipe() (io.WriteCloser, error) {
	return ioutil.WriteNopCloser(c.conn), nil
}

// StdoutPipe return the underlying connection as Reader
func (c *command) StdoutPipe() (io.Reader, error) {
	return c.conn, nil
}

func endpointToCommand(cmd string, ep transport.Endpoint) string {
	return fmt.Sprintf("%s %s%chost=%s%c", cmd, ep.Path, 0, ep.Host, 0)
}

// Wait no-op function, required by the interface
func (c *command) Wait() error {
	return nil
}

// Close closes the TCP connection and connection.
func (c *command) Close() error {
	if !c.connected {
		return nil
	}

	c.connected = false
	return c.conn.Close()
}