aboutsummaryrefslogtreecommitdiffstats
path: root/plumbing/transport/common.go
blob: bfc999f15c78b2a3158b03d2c384040be7eeb453 (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
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
// Package transport includes the implementation for different transport
// protocols.
//
// `Client` can be used to fetch and send packfiles to a git server.
// The `client` package provides higher level functions to instantiate the
// appropiate `Client` based on the repository URL.
//
// Go-git supports HTTP and SSH (see `Protocols`), but you can also install
// your own protocols (see the `client` package).
//
// Each protocol has its own implementation of `Client`, but you should
// generally not use them directly, use `client.NewClient` instead.
package transport

import (
	"errors"
	"fmt"
	"io"
	"net/url"
	"regexp"

	"gopkg.in/src-d/go-git.v4/plumbing"
	"gopkg.in/src-d/go-git.v4/plumbing/protocol/packp"
	"gopkg.in/src-d/go-git.v4/plumbing/protocol/packp/capability"
)

var (
	ErrRepositoryNotFound     = errors.New("repository not found")
	ErrEmptyRemoteRepository  = errors.New("remote repository is empty")
	ErrAuthorizationRequired  = errors.New("authorization required")
	ErrEmptyUploadPackRequest = errors.New("empty git-upload-pack given")
	ErrInvalidAuthMethod      = errors.New("invalid auth method")

	ErrAdvertistedReferencesAlreadyCalled = errors.New("cannot call AdvertisedReference twice")
)

const (
	UploadPackServiceName  = "git-upload-pack"
	ReceivePackServiceName = "git-receive-pack"
)

// Client can initiate git-fetch-pack and git-send-pack processes.
type Client interface {
	// NewFetchPackSession starts a git-fetch-pack session for an endpoint.
	NewFetchPackSession(Endpoint) (FetchPackSession, error)
	// NewSendPackSession starts a git-send-pack session for an endpoint.
	NewSendPackSession(Endpoint) (SendPackSession, error)
}

type Session interface {
	SetAuth(auth AuthMethod) error
	io.Closer
}

type AuthMethod interface {
	fmt.Stringer
	Name() string
}

// FetchPackSession represents a git-fetch-pack session.
// A git-fetch-pack session has two steps: reference discovery
// (`AdvertisedReferences` function) and fetching pack (`FetchPack` function).
// In that order.
type FetchPackSession interface {
	Session
	// AdvertisedReferences retrieves the advertised references for a
	// repository. It should be called before FetchPack, and it cannot be
	// called after FetchPack.
	AdvertisedReferences() (*packp.AdvRefs, error)
	// FetchPack takes a request and returns a reader for the packfile
	// received from the server.
	FetchPack(req *packp.UploadPackRequest) (io.ReadCloser, error)
}

// FetchPackSession represents a git-send-pack session.
// A git-send-pack session has two steps: reference discovery
// (`AdvertisedReferences` function) and sending pack (`SendPack` function).
// In that order.
type SendPackSession interface {
	Session
	// AdvertisedReferences retrieves the advertised references for a
	// repository. It should be called before FetchPack, and it cannot be
	// called after FetchPack.
	AdvertisedReferences() (*packp.AdvRefs, error)
	// UpdateReferences sends an update references request and returns a
	// writer to be used for packfile writing.
	//TODO: Complete signature.
	SendPack() (io.WriteCloser, error)
}

type Endpoint url.URL

var (
	isSchemeRegExp   = regexp.MustCompile("^[^:]+://")
	scpLikeUrlRegExp = regexp.MustCompile("^(?P<user>[^@]+@)?(?P<host>[^:]+):/?(?P<path>.+)$")
)

func NewEndpoint(endpoint string) (Endpoint, error) {
	endpoint = transformSCPLikeIfNeeded(endpoint)

	u, err := url.Parse(endpoint)
	if err != nil {
		return Endpoint{}, plumbing.NewPermanentError(err)
	}

	if !u.IsAbs() {
		return Endpoint{}, plumbing.NewPermanentError(fmt.Errorf(
			"invalid endpoint: %s", endpoint,
		))
	}

	return Endpoint(*u), nil
}

func (e *Endpoint) String() string {
	u := url.URL(*e)
	return u.String()
}

func transformSCPLikeIfNeeded(endpoint string) string {
	if !isSchemeRegExp.MatchString(endpoint) && scpLikeUrlRegExp.MatchString(endpoint) {
		m := scpLikeUrlRegExp.FindStringSubmatch(endpoint)
		return fmt.Sprintf("ssh://%s%s/%s", m[1], m[2], m[3])
	}

	return endpoint
}

// UnsupportedCapabilities are the capabilities not supported by any client
// implementation
var UnsupportedCapabilities = []capability.Capability{
	capability.MultiACK,
	capability.MultiACKDetailed,
	capability.Sideband,
	capability.Sideband64k,
	capability.ThinPack,
}

// FilterUnsupportedCapabilities it filter out all the UnsupportedCapabilities
// from a capability.List, the intended usage is on the client implementation
// to filter the capabilities from an AdvRefs message.
func FilterUnsupportedCapabilities(list *capability.List) {
	for _, c := range UnsupportedCapabilities {
		list.Delete(c)
	}
}