gitea/vendor/gopkg.in/src-d/go-git.v4/plumbing/transport/common.go
2019-04-16 22:04:23 -04:00

275 lines
7.0 KiB
Go

// 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
// appropriate `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 (
"bytes"
"context"
"errors"
"fmt"
"io"
"net/url"
"strconv"
"strings"
giturl "gopkg.in/src-d/go-git.v4/internal/url"
"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")
ErrAuthenticationRequired = errors.New("authentication required")
ErrAuthorizationFailed = errors.New("authorization failed")
ErrEmptyUploadPackRequest = errors.New("empty git-upload-pack given")
ErrInvalidAuthMethod = errors.New("invalid auth method")
ErrAlreadyConnected = errors.New("session already established")
)
const (
UploadPackServiceName = "git-upload-pack"
ReceivePackServiceName = "git-receive-pack"
)
// Transport can initiate git-upload-pack and git-receive-pack processes.
// It is implemented both by the client and the server, making this a RPC.
type Transport interface {
// NewUploadPackSession starts a git-upload-pack session for an endpoint.
NewUploadPackSession(*Endpoint, AuthMethod) (UploadPackSession, error)
// NewReceivePackSession starts a git-receive-pack session for an endpoint.
NewReceivePackSession(*Endpoint, AuthMethod) (ReceivePackSession, error)
}
type Session interface {
// AdvertisedReferences retrieves the advertised references for a
// repository.
// If the repository does not exist, returns ErrRepositoryNotFound.
// If the repository exists, but is empty, returns ErrEmptyRemoteRepository.
AdvertisedReferences() (*packp.AdvRefs, error)
io.Closer
}
type AuthMethod interface {
fmt.Stringer
Name() string
}
// UploadPackSession represents a git-upload-pack session.
// A git-upload-pack session has two steps: reference discovery
// (AdvertisedReferences) and uploading pack (UploadPack).
type UploadPackSession interface {
Session
// UploadPack takes a git-upload-pack request and returns a response,
// including a packfile. Don't be confused by terminology, the client
// side of a git-upload-pack is called git-fetch-pack, although here
// the same interface is used to make it RPC-like.
UploadPack(context.Context, *packp.UploadPackRequest) (*packp.UploadPackResponse, error)
}
// ReceivePackSession represents a git-receive-pack session.
// A git-receive-pack session has two steps: reference discovery
// (AdvertisedReferences) and receiving pack (ReceivePack).
// In that order.
type ReceivePackSession interface {
Session
// ReceivePack sends an update references request and a packfile
// reader and returns a ReportStatus and error. Don't be confused by
// terminology, the client side of a git-receive-pack is called
// git-send-pack, although here the same interface is used to make it
// RPC-like.
ReceivePack(context.Context, *packp.ReferenceUpdateRequest) (*packp.ReportStatus, error)
}
// Endpoint represents a Git URL in any supported protocol.
type Endpoint struct {
// Protocol is the protocol of the endpoint (e.g. git, https, file).
Protocol string
// User is the user.
User string
// Password is the password.
Password string
// Host is the host.
Host string
// Port is the port to connect, if 0 the default port for the given protocol
// wil be used.
Port int
// Path is the repository path.
Path string
}
var defaultPorts = map[string]int{
"http": 80,
"https": 443,
"git": 9418,
"ssh": 22,
}
// String returns a string representation of the Git URL.
func (u *Endpoint) String() string {
var buf bytes.Buffer
if u.Protocol != "" {
buf.WriteString(u.Protocol)
buf.WriteByte(':')
}
if u.Protocol != "" || u.Host != "" || u.User != "" || u.Password != "" {
buf.WriteString("//")
if u.User != "" || u.Password != "" {
buf.WriteString(url.PathEscape(u.User))
if u.Password != "" {
buf.WriteByte(':')
buf.WriteString(url.PathEscape(u.Password))
}
buf.WriteByte('@')
}
if u.Host != "" {
buf.WriteString(u.Host)
if u.Port != 0 {
port, ok := defaultPorts[strings.ToLower(u.Protocol)]
if !ok || ok && port != u.Port {
fmt.Fprintf(&buf, ":%d", u.Port)
}
}
}
}
if u.Path != "" && u.Path[0] != '/' && u.Host != "" {
buf.WriteByte('/')
}
buf.WriteString(u.Path)
return buf.String()
}
func NewEndpoint(endpoint string) (*Endpoint, error) {
if e, ok := parseSCPLike(endpoint); ok {
return e, nil
}
if e, ok := parseFile(endpoint); ok {
return e, nil
}
return parseURL(endpoint)
}
func parseURL(endpoint string) (*Endpoint, error) {
u, err := url.Parse(endpoint)
if err != nil {
return nil, err
}
if !u.IsAbs() {
return nil, plumbing.NewPermanentError(fmt.Errorf(
"invalid endpoint: %s", endpoint,
))
}
var user, pass string
if u.User != nil {
user = u.User.Username()
pass, _ = u.User.Password()
}
return &Endpoint{
Protocol: u.Scheme,
User: user,
Password: pass,
Host: u.Hostname(),
Port: getPort(u),
Path: getPath(u),
}, nil
}
func getPort(u *url.URL) int {
p := u.Port()
if p == "" {
return 0
}
i, err := strconv.Atoi(p)
if err != nil {
return 0
}
return i
}
func getPath(u *url.URL) string {
var res string = u.Path
if u.RawQuery != "" {
res += "?" + u.RawQuery
}
if u.Fragment != "" {
res += "#" + u.Fragment
}
return res
}
func parseSCPLike(endpoint string) (*Endpoint, bool) {
if giturl.MatchesScheme(endpoint) || !giturl.MatchesScpLike(endpoint) {
return nil, false
}
user, host, portStr, path := giturl.FindScpLikeComponents(endpoint)
port, err := strconv.Atoi(portStr)
if err != nil {
port = 22
}
return &Endpoint{
Protocol: "ssh",
User: user,
Host: host,
Port: port,
Path: path,
}, true
}
func parseFile(endpoint string) (*Endpoint, bool) {
if giturl.MatchesScheme(endpoint) {
return nil, false
}
path := endpoint
return &Endpoint{
Protocol: "file",
Path: path,
}, true
}
// UnsupportedCapabilities are the capabilities not supported by any client
// implementation
var UnsupportedCapabilities = []capability.Capability{
capability.MultiACK,
capability.MultiACKDetailed,
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)
}
}