git-lfs/lfsapi/lfsapi.go
2017-01-03 13:05:30 -07:00

173 lines
3.6 KiB
Go

package lfsapi
import (
"encoding/json"
"fmt"
"io"
"net/http"
"regexp"
"strconv"
"strings"
"sync"
"github.com/git-lfs/git-lfs/errors"
)
var (
lfsMediaTypeRE = regexp.MustCompile(`\Aapplication/vnd\.git\-lfs\+json(;|\z)`)
jsonMediaTypeRE = regexp.MustCompile(`\Aapplication/json(;|\z)`)
)
type Client struct {
Endpoints EndpointFinder
Credentials CredentialHelper
Netrc NetrcFinder
DialTimeout int
KeepaliveTimeout int
TLSTimeout int
ConcurrentTransfers int
HTTPSProxy string
HTTPProxy string
NoProxy string
SkipSSLVerify bool
Verbose bool
DebuggingVerbose bool
LoggingStats bool
VerboseOut io.Writer
hostClients map[string]*http.Client
clientMu sync.Mutex
transferBuckets map[string][]*http.Response
transferBucketMu sync.Mutex
transfers map[*http.Response]*httpTransfer
transferMu sync.Mutex
// only used for per-host ssl certs
gitEnv env
osEnv env
}
func NewClient(osEnv env, gitEnv env) (*Client, error) {
if osEnv == nil {
osEnv = make(Env)
}
if gitEnv == nil {
gitEnv = make(Env)
}
netrc, err := ParseNetrc(osEnv)
if err != nil {
return nil, err
}
httpsProxy, httpProxy, noProxy := getProxyServers(osEnv, gitEnv)
c := &Client{
Endpoints: NewEndpointFinder(gitEnv),
Credentials: &CommandCredentialHelper{
SkipPrompt: !osEnv.Bool("GIT_TERMINAL_PROMPT", true),
},
Netrc: netrc,
DialTimeout: gitEnv.Int("lfs.dialtimeout", 0),
KeepaliveTimeout: gitEnv.Int("lfs.keepalive", 0),
TLSTimeout: gitEnv.Int("lfs.tlstimeout", 0),
ConcurrentTransfers: gitEnv.Int("lfs.concurrenttransfers", 0),
SkipSSLVerify: !gitEnv.Bool("http.sslverify", true) || osEnv.Bool("GIT_SSL_NO_VERIFY", false),
Verbose: osEnv.Bool("GIT_CURL_VERBOSE", false),
DebuggingVerbose: osEnv.Bool("LFS_DEBUG_HTTP", false),
LoggingStats: osEnv.Bool("GIT_LOG_STATS", false),
HTTPSProxy: httpsProxy,
HTTPProxy: httpProxy,
NoProxy: noProxy,
gitEnv: gitEnv,
osEnv: osEnv,
}
return c, nil
}
func IsDecodeTypeError(err error) bool {
_, ok := err.(*decodeTypeError)
return ok
}
type decodeTypeError struct {
Type string
}
func (e *decodeTypeError) TypeError() {}
func (e *decodeTypeError) Error() string {
return fmt.Sprintf("Expected json type, got: %q", e.Type)
}
func DecodeJSON(res *http.Response, obj interface{}) error {
ctype := res.Header.Get("Content-Type")
if !(lfsMediaTypeRE.MatchString(ctype) || jsonMediaTypeRE.MatchString(ctype)) {
return &decodeTypeError{Type: ctype}
}
err := json.NewDecoder(res.Body).Decode(obj)
res.Body.Close()
if err != nil {
return errors.Wrapf(err, "Unable to parse HTTP response for %s %s", res.Request.Method, res.Request.URL)
}
return nil
}
type env interface {
Get(string) (string, bool)
Int(string, int) int
Bool(string, bool) bool
All() map[string]string
}
// basic config.Environment implementation. Only used in tests, or as a zero
// value to NewClient().
type Env map[string]string
func (e Env) Get(key string) (string, bool) {
v, ok := e[key]
return v, ok
}
func (e Env) Int(key string, def int) (val int) {
s, _ := e.Get(key)
if len(s) == 0 {
return def
}
i, err := strconv.Atoi(s)
if err != nil {
return def
}
return i
}
func (e Env) Bool(key string, def bool) (val bool) {
s, _ := e.Get(key)
if len(s) == 0 {
return def
}
switch strings.ToLower(s) {
case "true", "1", "on", "yes", "t":
return true
case "false", "0", "off", "no", "f":
return false
default:
return false
}
}
func (e Env) All() map[string]string {
return e
}