Merge pull request #3244 from git-lfs/lfshttp
Create new lfshttp package
This commit is contained in:
commit
002c2632bb
1
Makefile
1
Makefile
@ -89,6 +89,7 @@ PKGS += git/githistory
|
|||||||
PKGS += git
|
PKGS += git
|
||||||
PKGS += lfs
|
PKGS += lfs
|
||||||
PKGS += lfsapi
|
PKGS += lfsapi
|
||||||
|
PKGS += lfshttp
|
||||||
PKGS += locking
|
PKGS += locking
|
||||||
PKGS += subprocess
|
PKGS += subprocess
|
||||||
PKGS += tasklog
|
PKGS += tasklog
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
package commands
|
package commands
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/git-lfs/git-lfs/lfsapi"
|
"github.com/git-lfs/git-lfs/lfshttp"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -10,7 +10,7 @@ var (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func versionCommand(cmd *cobra.Command, args []string) {
|
func versionCommand(cmd *cobra.Command, args []string) {
|
||||||
Print(lfsapi.UserAgent)
|
Print(lfshttp.UserAgent)
|
||||||
|
|
||||||
if lovesComics {
|
if lovesComics {
|
||||||
Print("Nothing may see Gah Lak Tus and survive!")
|
Print("Nothing may see Gah Lak Tus and survive!")
|
||||||
|
@ -9,7 +9,7 @@ import (
|
|||||||
"github.com/git-lfs/git-lfs/config"
|
"github.com/git-lfs/git-lfs/config"
|
||||||
"github.com/git-lfs/git-lfs/errors"
|
"github.com/git-lfs/git-lfs/errors"
|
||||||
"github.com/git-lfs/git-lfs/git"
|
"github.com/git-lfs/git-lfs/git"
|
||||||
"github.com/git-lfs/git-lfs/lfsapi"
|
"github.com/git-lfs/git-lfs/lfshttp"
|
||||||
"github.com/git-lfs/git-lfs/locking"
|
"github.com/git-lfs/git-lfs/locking"
|
||||||
"github.com/git-lfs/git-lfs/tq"
|
"github.com/git-lfs/git-lfs/tq"
|
||||||
)
|
)
|
||||||
@ -30,7 +30,7 @@ func verifyLocksForUpdates(lv *lockVerifier, updates []*git.RefUpdate) {
|
|||||||
|
|
||||||
// lockVerifier verifies locked files before updating one or more refs.
|
// lockVerifier verifies locked files before updating one or more refs.
|
||||||
type lockVerifier struct {
|
type lockVerifier struct {
|
||||||
endpoint lfsapi.Endpoint
|
endpoint lfshttp.Endpoint
|
||||||
verifyState verifyState
|
verifyState verifyState
|
||||||
verifiedRefs map[string]bool
|
verifiedRefs map[string]bool
|
||||||
|
|
||||||
|
@ -44,7 +44,7 @@ func Environ(cfg *config.Configuration, manifest *tq.Manifest) []string {
|
|||||||
fmt.Sprintf("LocalMediaDir=%s", cfg.LFSObjectDir()),
|
fmt.Sprintf("LocalMediaDir=%s", cfg.LFSObjectDir()),
|
||||||
fmt.Sprintf("LocalReferenceDirs=%s", references),
|
fmt.Sprintf("LocalReferenceDirs=%s", references),
|
||||||
fmt.Sprintf("TempDir=%s", cfg.TempDir()),
|
fmt.Sprintf("TempDir=%s", cfg.TempDir()),
|
||||||
fmt.Sprintf("ConcurrentTransfers=%d", api.ConcurrentTransfers),
|
fmt.Sprintf("ConcurrentTransfers=%d", api.ConcurrentTransfers()),
|
||||||
fmt.Sprintf("TusTransfers=%v", cfg.TusTransfersAllowed()),
|
fmt.Sprintf("TusTransfers=%v", cfg.TusTransfersAllowed()),
|
||||||
fmt.Sprintf("BasicTransfersOnly=%v", cfg.BasicTransfersOnly()),
|
fmt.Sprintf("BasicTransfersOnly=%v", cfg.BasicTransfersOnly()),
|
||||||
fmt.Sprintf("SkipDownloadErrors=%v", cfg.SkipDownloadErrors()),
|
fmt.Sprintf("SkipDownloadErrors=%v", cfg.SkipDownloadErrors()),
|
||||||
|
@ -10,6 +10,7 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/git-lfs/git-lfs/errors"
|
"github.com/git-lfs/git-lfs/errors"
|
||||||
|
"github.com/git-lfs/git-lfs/lfshttp"
|
||||||
"github.com/git-lfs/go-netrc/netrc"
|
"github.com/git-lfs/go-netrc/netrc"
|
||||||
"github.com/rubyist/tracerx"
|
"github.com/rubyist/tracerx"
|
||||||
)
|
)
|
||||||
@ -28,7 +29,7 @@ func (c *Client) DoWithAuth(remote string, req *http.Request) (*http.Response, e
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (c *Client) doWithAuth(remote string, req *http.Request, via []*http.Request) (*http.Response, error) {
|
func (c *Client) doWithAuth(remote string, req *http.Request, via []*http.Request) (*http.Response, error) {
|
||||||
req.Header = c.extraHeadersFor(req)
|
req.Header = c.client.ExtraHeadersFor(req)
|
||||||
|
|
||||||
apiEndpoint, access, credHelper, credsURL, creds, err := c.getCreds(remote, req)
|
apiEndpoint, access, credHelper, credsURL, creds, err := c.getCreds(remote, req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -71,7 +72,19 @@ func (c *Client) doWithCreds(req *http.Request, credHelper CredentialHelper, cre
|
|||||||
if access == NTLMAccess {
|
if access == NTLMAccess {
|
||||||
return c.doWithNTLM(req, credHelper, creds, credsURL)
|
return c.doWithNTLM(req, credHelper, creds, credsURL)
|
||||||
}
|
}
|
||||||
return c.do(req, "", via)
|
|
||||||
|
req.Header.Set("User-Agent", lfshttp.UserAgent)
|
||||||
|
|
||||||
|
redirectedReq, res, err := c.client.DoWithRedirect(c.client.HttpClient(req.Host), req, "", via)
|
||||||
|
if err != nil || res != nil {
|
||||||
|
return res, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if redirectedReq == nil {
|
||||||
|
return res, errors.New("failed to redirect request")
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.doWithAuth("", redirectedReq, via)
|
||||||
}
|
}
|
||||||
|
|
||||||
// getCreds fills the authorization header for the given request if possible,
|
// getCreds fills the authorization header for the given request if possible,
|
||||||
@ -94,7 +107,7 @@ func (c *Client) doWithCreds(req *http.Request, credHelper CredentialHelper, cre
|
|||||||
// 3. The Git Remote URL, which should be something like "https://git.com/repo.git"
|
// 3. The Git Remote URL, which should be something like "https://git.com/repo.git"
|
||||||
// This URL is used for the Git Credential Helper. This way existing https
|
// This URL is used for the Git Credential Helper. This way existing https
|
||||||
// Git remote credentials can be re-used for LFS.
|
// Git remote credentials can be re-used for LFS.
|
||||||
func (c *Client) getCreds(remote string, req *http.Request) (Endpoint, Access, CredentialHelper, *url.URL, Creds, error) {
|
func (c *Client) getCreds(remote string, req *http.Request) (lfshttp.Endpoint, Access, CredentialHelper, *url.URL, Creds, error) {
|
||||||
ef := c.Endpoints
|
ef := c.Endpoints
|
||||||
if ef == nil {
|
if ef == nil {
|
||||||
ef = defaultEndpointFinder
|
ef = defaultEndpointFinder
|
||||||
@ -198,7 +211,7 @@ func setAuthFromNetrc(netrcFinder NetrcFinder, req *http.Request) bool {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
func getCredURLForAPI(ef EndpointFinder, operation, remote string, apiEndpoint Endpoint, req *http.Request) (*url.URL, error) {
|
func getCredURLForAPI(ef EndpointFinder, operation, remote string, apiEndpoint lfshttp.Endpoint, req *http.Request) (*url.URL, error) {
|
||||||
apiURL, err := url.Parse(apiEndpoint.Url)
|
apiURL, err := url.Parse(apiEndpoint.Url)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
@ -12,6 +12,7 @@ import (
|
|||||||
|
|
||||||
"github.com/git-lfs/git-lfs/errors"
|
"github.com/git-lfs/git-lfs/errors"
|
||||||
"github.com/git-lfs/git-lfs/git"
|
"github.com/git-lfs/git-lfs/git"
|
||||||
|
"github.com/git-lfs/git-lfs/lfshttp"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
@ -73,7 +74,7 @@ func TestDoWithAuthApprove(t *testing.T) {
|
|||||||
defer srv.Close()
|
defer srv.Close()
|
||||||
|
|
||||||
creds := newMockCredentialHelper()
|
creds := newMockCredentialHelper()
|
||||||
c, err := NewClient(NewContext(nil, nil, map[string]string{
|
c, err := NewClient(lfshttp.NewContext(nil, nil, map[string]string{
|
||||||
"lfs.url": srv.URL + "/repo/lfs",
|
"lfs.url": srv.URL + "/repo/lfs",
|
||||||
}))
|
}))
|
||||||
require.Nil(t, err)
|
require.Nil(t, err)
|
||||||
@ -143,7 +144,7 @@ func TestDoWithAuthReject(t *testing.T) {
|
|||||||
|
|
||||||
c, _ := NewClient(nil)
|
c, _ := NewClient(nil)
|
||||||
c.Credentials = creds
|
c.Credentials = creds
|
||||||
c.Endpoints = NewEndpointFinder(NewContext(nil, nil, map[string]string{
|
c.Endpoints = NewEndpointFinder(lfshttp.NewContext(nil, nil, map[string]string{
|
||||||
"lfs.url": srv.URL,
|
"lfs.url": srv.URL,
|
||||||
}))
|
}))
|
||||||
|
|
||||||
@ -567,7 +568,7 @@ func TestGetCreds(t *testing.T) {
|
|||||||
req.Header.Set(key, value)
|
req.Header.Set(key, value)
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx := NewContext(git.NewConfig("", ""), nil, test.Config)
|
ctx := lfshttp.NewContext(git.NewConfig("", ""), nil, test.Config)
|
||||||
client, _ := NewClient(ctx)
|
client, _ := NewClient(ctx)
|
||||||
client.Credentials = &fakeCredentialFiller{}
|
client.Credentials = &fakeCredentialFiller{}
|
||||||
client.Netrc = &fakeNetrc{}
|
client.Netrc = &fakeNetrc{}
|
||||||
@ -617,3 +618,77 @@ func (f *fakeCredentialFiller) Approve(creds Creds) error {
|
|||||||
func (f *fakeCredentialFiller) Reject(creds Creds) error {
|
func (f *fakeCredentialFiller) Reject(creds Creds) error {
|
||||||
return errors.New("Not implemented")
|
return errors.New("Not implemented")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestClientRedirectReauthenticate(t *testing.T) {
|
||||||
|
var srv1, srv2 *httptest.Server
|
||||||
|
var called1, called2 uint32
|
||||||
|
var creds1, creds2 Creds
|
||||||
|
|
||||||
|
srv1 = httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
atomic.AddUint32(&called1, 1)
|
||||||
|
|
||||||
|
if hdr := r.Header.Get("Authorization"); len(hdr) > 0 {
|
||||||
|
parts := strings.SplitN(hdr, " ", 2)
|
||||||
|
typ, b64 := parts[0], parts[1]
|
||||||
|
|
||||||
|
auth, err := base64.URLEncoding.DecodeString(b64)
|
||||||
|
assert.Nil(t, err)
|
||||||
|
assert.Equal(t, "Basic", typ)
|
||||||
|
assert.Equal(t, "user1:pass1", string(auth))
|
||||||
|
|
||||||
|
http.Redirect(w, r, srv2.URL+r.URL.Path, http.StatusMovedPermanently)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
w.WriteHeader(http.StatusUnauthorized)
|
||||||
|
}))
|
||||||
|
|
||||||
|
srv2 = httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
atomic.AddUint32(&called2, 1)
|
||||||
|
|
||||||
|
parts := strings.SplitN(r.Header.Get("Authorization"), " ", 2)
|
||||||
|
typ, b64 := parts[0], parts[1]
|
||||||
|
|
||||||
|
auth, err := base64.URLEncoding.DecodeString(b64)
|
||||||
|
assert.Nil(t, err)
|
||||||
|
assert.Equal(t, "Basic", typ)
|
||||||
|
assert.Equal(t, "user2:pass2", string(auth))
|
||||||
|
}))
|
||||||
|
|
||||||
|
// Change the URL of srv2 to make it appears as if it is a different
|
||||||
|
// host.
|
||||||
|
srv2.URL = strings.Replace(srv2.URL, "127.0.0.1", "0.0.0.0", 1)
|
||||||
|
|
||||||
|
creds1 = Creds(map[string]string{
|
||||||
|
"protocol": "http",
|
||||||
|
"host": strings.TrimPrefix(srv1.URL, "http://"),
|
||||||
|
|
||||||
|
"username": "user1",
|
||||||
|
"password": "pass1",
|
||||||
|
})
|
||||||
|
creds2 = Creds(map[string]string{
|
||||||
|
"protocol": "http",
|
||||||
|
"host": strings.TrimPrefix(srv2.URL, "http://"),
|
||||||
|
|
||||||
|
"username": "user2",
|
||||||
|
"password": "pass2",
|
||||||
|
})
|
||||||
|
|
||||||
|
defer srv1.Close()
|
||||||
|
defer srv2.Close()
|
||||||
|
|
||||||
|
c, err := NewClient(lfshttp.NewContext(nil, nil, nil))
|
||||||
|
creds := newCredentialCacher()
|
||||||
|
creds.Approve(creds1)
|
||||||
|
creds.Approve(creds2)
|
||||||
|
c.Credentials = creds
|
||||||
|
|
||||||
|
req, err := http.NewRequest("GET", srv1.URL, nil)
|
||||||
|
require.Nil(t, err)
|
||||||
|
|
||||||
|
_, err = c.DoWithAuth("", req)
|
||||||
|
assert.Nil(t, err)
|
||||||
|
|
||||||
|
// called1 is 2 since LFS tries an unauthenticated request first
|
||||||
|
assert.EqualValues(t, 2, called1)
|
||||||
|
assert.EqualValues(t, 1, called2)
|
||||||
|
}
|
||||||
|
429
lfsapi/client.go
429
lfsapi/client.go
@ -1,431 +1,50 @@
|
|||||||
package lfsapi
|
package lfsapi
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
|
||||||
"crypto/tls"
|
|
||||||
"fmt"
|
|
||||||
"io"
|
"io"
|
||||||
"net"
|
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/textproto"
|
|
||||||
"net/url"
|
|
||||||
"os"
|
|
||||||
"regexp"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/git-lfs/git-lfs/config"
|
"github.com/git-lfs/git-lfs/config"
|
||||||
"github.com/git-lfs/git-lfs/errors"
|
"github.com/git-lfs/git-lfs/lfshttp"
|
||||||
"github.com/git-lfs/git-lfs/tools"
|
|
||||||
"github.com/rubyist/tracerx"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const MediaType = "application/vnd.git-lfs+json; charset=utf-8"
|
func (c *Client) NewRequest(method string, e lfshttp.Endpoint, suffix string, body interface{}) (*http.Request, error) {
|
||||||
|
return c.client.NewRequest(method, e, suffix, body)
|
||||||
var (
|
|
||||||
UserAgent = "git-lfs"
|
|
||||||
httpRE = regexp.MustCompile(`\Ahttps?://`)
|
|
||||||
)
|
|
||||||
|
|
||||||
var hintFileUrl = strings.TrimSpace(`
|
|
||||||
hint: The remote resolves to a file:// URL, which can only work with a
|
|
||||||
hint: standalone transfer agent. See section "Using a Custom Transfer Type
|
|
||||||
hint: without the API server" in custom-transfers.md for details.
|
|
||||||
`)
|
|
||||||
|
|
||||||
func (c *Client) NewRequest(method string, e Endpoint, suffix string, body interface{}) (*http.Request, error) {
|
|
||||||
if strings.HasPrefix(e.Url, "file://") {
|
|
||||||
// Initial `\n` to avoid overprinting `Downloading LFS...`.
|
|
||||||
fmt.Fprintf(os.Stderr, "\n%s\n", hintFileUrl)
|
|
||||||
}
|
|
||||||
|
|
||||||
sshRes, err := c.sshResolveWithRetries(e, method)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
prefix := e.Url
|
|
||||||
if len(sshRes.Href) > 0 {
|
|
||||||
prefix = sshRes.Href
|
|
||||||
}
|
|
||||||
|
|
||||||
if !httpRE.MatchString(prefix) {
|
|
||||||
urlfragment := strings.SplitN(prefix, "?", 2)[0]
|
|
||||||
return nil, fmt.Errorf("missing protocol: %q", urlfragment)
|
|
||||||
}
|
|
||||||
|
|
||||||
req, err := http.NewRequest(method, joinURL(prefix, suffix), nil)
|
|
||||||
if err != nil {
|
|
||||||
return req, err
|
|
||||||
}
|
|
||||||
|
|
||||||
for key, value := range sshRes.Header {
|
|
||||||
req.Header.Set(key, value)
|
|
||||||
}
|
|
||||||
req.Header.Set("Accept", MediaType)
|
|
||||||
|
|
||||||
if body != nil {
|
|
||||||
if merr := MarshalToRequest(req, body); merr != nil {
|
|
||||||
return req, merr
|
|
||||||
}
|
|
||||||
req.Header.Set("Content-Type", MediaType)
|
|
||||||
}
|
|
||||||
|
|
||||||
return req, err
|
|
||||||
}
|
|
||||||
|
|
||||||
const slash = "/"
|
|
||||||
|
|
||||||
func joinURL(prefix, suffix string) string {
|
|
||||||
if strings.HasSuffix(prefix, slash) {
|
|
||||||
return prefix + suffix
|
|
||||||
}
|
|
||||||
return prefix + slash + suffix
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Do sends an HTTP request to get an HTTP response. It wraps net/http, adding
|
// Do sends an HTTP request to get an HTTP response. It wraps net/http, adding
|
||||||
// extra headers, redirection handling, and error reporting.
|
// extra headers, redirection handling, and error reporting.
|
||||||
func (c *Client) Do(req *http.Request) (*http.Response, error) {
|
func (c *Client) Do(req *http.Request) (*http.Response, error) {
|
||||||
req.Header = c.extraHeadersFor(req)
|
return c.client.Do(req)
|
||||||
|
|
||||||
return c.do(req, "", nil)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// do performs an *http.Request respecting redirects, and handles the response
|
// do performs an *http.Request respecting redirects, and handles the response
|
||||||
// as defined in c.handleResponse. Notably, it does not alter the headers for
|
// as defined in c.handleResponse. Notably, it does not alter the headers for
|
||||||
// the request argument in any way.
|
// the request argument in any way.
|
||||||
func (c *Client) do(req *http.Request, remote string, via []*http.Request) (*http.Response, error) {
|
func (c *Client) do(req *http.Request, remote string, via []*http.Request) (*http.Response, error) {
|
||||||
req.Header.Set("User-Agent", UserAgent)
|
return c.client.Do(req)
|
||||||
|
|
||||||
res, err := c.doWithRedirects(c.httpClient(req.Host), req, remote, via)
|
|
||||||
if err != nil {
|
|
||||||
return res, err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return res, c.handleResponse(res)
|
func (c *Client) LogRequest(r *http.Request, reqKey string) *http.Request {
|
||||||
|
return c.client.LogRequest(r, reqKey)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) GitEnv() config.Environment {
|
||||||
|
return c.client.GitEnv()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) OSEnv() config.Environment {
|
||||||
|
return c.client.OSEnv()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) ConcurrentTransfers() int {
|
||||||
|
return c.client.ConcurrentTransfers
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) LogHTTPStats(w io.WriteCloser) {
|
||||||
|
c.client.LogHTTPStats(w)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Close closes any resources that this client opened.
|
|
||||||
func (c *Client) Close() error {
|
func (c *Client) Close() error {
|
||||||
return c.httpLogger.Close()
|
return c.client.Close()
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Client) sshResolveWithRetries(e Endpoint, method string) (*sshAuthResponse, error) {
|
|
||||||
var sshRes sshAuthResponse
|
|
||||||
var err error
|
|
||||||
|
|
||||||
requests := tools.MaxInt(0, c.sshTries) + 1
|
|
||||||
for i := 0; i < requests; i++ {
|
|
||||||
sshRes, err = c.SSH.Resolve(e, method)
|
|
||||||
if err == nil {
|
|
||||||
return &sshRes, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
tracerx.Printf(
|
|
||||||
"ssh: %s failed, error: %s, message: %s (try: %d/%d)",
|
|
||||||
e.SshUserAndHost, err.Error(), sshRes.Message, i,
|
|
||||||
requests,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(sshRes.Message) > 0 {
|
|
||||||
return nil, errors.Wrap(err, sshRes.Message)
|
|
||||||
}
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Client) extraHeadersFor(req *http.Request) http.Header {
|
|
||||||
extraHeaders := c.extraHeaders(req.URL)
|
|
||||||
if len(extraHeaders) == 0 {
|
|
||||||
return req.Header
|
|
||||||
}
|
|
||||||
|
|
||||||
copy := make(http.Header, len(req.Header))
|
|
||||||
for k, vs := range req.Header {
|
|
||||||
copy[k] = vs
|
|
||||||
}
|
|
||||||
|
|
||||||
for k, vs := range extraHeaders {
|
|
||||||
for _, v := range vs {
|
|
||||||
copy[k] = append(copy[k], v)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return copy
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Client) extraHeaders(u *url.URL) map[string][]string {
|
|
||||||
hdrs := c.uc.GetAll("http", u.String(), "extraHeader")
|
|
||||||
m := make(map[string][]string, len(hdrs))
|
|
||||||
|
|
||||||
for _, hdr := range hdrs {
|
|
||||||
parts := strings.SplitN(hdr, ":", 2)
|
|
||||||
if len(parts) < 2 {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
k, v := parts[0], strings.TrimSpace(parts[1])
|
|
||||||
// If header keys are given in non-canonicalized form (e.g.,
|
|
||||||
// "AUTHORIZATION" as opposed to "Authorization") they will not
|
|
||||||
// be returned in calls to net/http.Header.Get().
|
|
||||||
//
|
|
||||||
// So, we avoid this problem by first canonicalizing header keys
|
|
||||||
// for extra headers.
|
|
||||||
k = textproto.CanonicalMIMEHeaderKey(k)
|
|
||||||
|
|
||||||
m[k] = append(m[k], v)
|
|
||||||
}
|
|
||||||
return m
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Client) doWithRedirects(cli *http.Client, req *http.Request, remote string, via []*http.Request) (*http.Response, error) {
|
|
||||||
tracedReq, err := c.traceRequest(req)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
var retries int
|
|
||||||
if n, ok := Retries(req); ok {
|
|
||||||
retries = n
|
|
||||||
} else {
|
|
||||||
retries = defaultRequestRetries
|
|
||||||
}
|
|
||||||
|
|
||||||
var res *http.Response
|
|
||||||
|
|
||||||
requests := tools.MaxInt(0, retries) + 1
|
|
||||||
for i := 0; i < requests; i++ {
|
|
||||||
res, err = cli.Do(req)
|
|
||||||
if err == nil {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
if seek, ok := req.Body.(io.Seeker); ok {
|
|
||||||
seek.Seek(0, io.SeekStart)
|
|
||||||
}
|
|
||||||
|
|
||||||
c.traceResponse(req, tracedReq, nil)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
c.traceResponse(req, tracedReq, nil)
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if res == nil {
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
c.traceResponse(req, tracedReq, res)
|
|
||||||
|
|
||||||
if res.StatusCode != 301 &&
|
|
||||||
res.StatusCode != 302 &&
|
|
||||||
res.StatusCode != 303 &&
|
|
||||||
res.StatusCode != 307 &&
|
|
||||||
res.StatusCode != 308 {
|
|
||||||
|
|
||||||
// Above are the list of 3xx status codes that we know
|
|
||||||
// how to handle below. If the status code contained in
|
|
||||||
// the HTTP response was none of them, return the (res,
|
|
||||||
// err) tuple as-is, otherwise handle the redirect.
|
|
||||||
return res, err
|
|
||||||
}
|
|
||||||
|
|
||||||
redirectTo := res.Header.Get("Location")
|
|
||||||
locurl, err := url.Parse(redirectTo)
|
|
||||||
if err == nil && !locurl.IsAbs() {
|
|
||||||
locurl = req.URL.ResolveReference(locurl)
|
|
||||||
redirectTo = locurl.String()
|
|
||||||
}
|
|
||||||
|
|
||||||
via = append(via, req)
|
|
||||||
if len(via) >= 3 {
|
|
||||||
return res, errors.New("too many redirects")
|
|
||||||
}
|
|
||||||
|
|
||||||
redirectedReq, err := newRequestForRetry(req, redirectTo)
|
|
||||||
if err != nil {
|
|
||||||
return res, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(req.Header.Get("Authorization")) > 0 {
|
|
||||||
// If the original request was authenticated (noted by the
|
|
||||||
// presence of the Authorization header), then recur through
|
|
||||||
// doWithAuth, retaining the requests via but only after
|
|
||||||
// authenticating the redirected request.
|
|
||||||
return c.doWithAuth(remote, redirectedReq, via)
|
|
||||||
}
|
|
||||||
return c.doWithRedirects(cli, redirectedReq, remote, via)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Client) httpClient(host string) *http.Client {
|
|
||||||
c.clientMu.Lock()
|
|
||||||
defer c.clientMu.Unlock()
|
|
||||||
|
|
||||||
if c.gitEnv == nil {
|
|
||||||
c.gitEnv = make(testEnv)
|
|
||||||
}
|
|
||||||
|
|
||||||
if c.osEnv == nil {
|
|
||||||
c.osEnv = make(testEnv)
|
|
||||||
}
|
|
||||||
|
|
||||||
if c.hostClients == nil {
|
|
||||||
c.hostClients = make(map[string]*http.Client)
|
|
||||||
}
|
|
||||||
|
|
||||||
if client, ok := c.hostClients[host]; ok {
|
|
||||||
return client
|
|
||||||
}
|
|
||||||
|
|
||||||
concurrentTransfers := c.ConcurrentTransfers
|
|
||||||
if concurrentTransfers < 1 {
|
|
||||||
concurrentTransfers = 8
|
|
||||||
}
|
|
||||||
|
|
||||||
dialtime := c.DialTimeout
|
|
||||||
if dialtime < 1 {
|
|
||||||
dialtime = 30
|
|
||||||
}
|
|
||||||
|
|
||||||
keepalivetime := c.KeepaliveTimeout
|
|
||||||
if keepalivetime < 1 {
|
|
||||||
keepalivetime = 1800
|
|
||||||
}
|
|
||||||
|
|
||||||
tlstime := c.TLSTimeout
|
|
||||||
if tlstime < 1 {
|
|
||||||
tlstime = 30
|
|
||||||
}
|
|
||||||
|
|
||||||
tr := &http.Transport{
|
|
||||||
Proxy: proxyFromClient(c),
|
|
||||||
TLSHandshakeTimeout: time.Duration(tlstime) * time.Second,
|
|
||||||
MaxIdleConnsPerHost: concurrentTransfers,
|
|
||||||
}
|
|
||||||
|
|
||||||
activityTimeout := 30
|
|
||||||
if v, ok := c.uc.Get("lfs", fmt.Sprintf("https://%v", host), "activitytimeout"); ok {
|
|
||||||
if i, err := strconv.Atoi(v); err == nil {
|
|
||||||
activityTimeout = i
|
|
||||||
} else {
|
|
||||||
activityTimeout = 0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
dialer := &net.Dialer{
|
|
||||||
Timeout: time.Duration(dialtime) * time.Second,
|
|
||||||
KeepAlive: time.Duration(keepalivetime) * time.Second,
|
|
||||||
DualStack: true,
|
|
||||||
}
|
|
||||||
|
|
||||||
if activityTimeout > 0 {
|
|
||||||
activityDuration := time.Duration(activityTimeout) * time.Second
|
|
||||||
tr.DialContext = func(ctx context.Context, network, addr string) (net.Conn, error) {
|
|
||||||
c, err := dialer.DialContext(ctx, network, addr)
|
|
||||||
if c == nil {
|
|
||||||
return c, err
|
|
||||||
}
|
|
||||||
if tc, ok := c.(*net.TCPConn); ok {
|
|
||||||
tc.SetKeepAlive(true)
|
|
||||||
tc.SetKeepAlivePeriod(dialer.KeepAlive)
|
|
||||||
}
|
|
||||||
return &deadlineConn{Timeout: activityDuration, Conn: c}, err
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
tr.DialContext = dialer.DialContext
|
|
||||||
}
|
|
||||||
|
|
||||||
tr.TLSClientConfig = &tls.Config{}
|
|
||||||
|
|
||||||
if isClientCertEnabledForHost(c, host) {
|
|
||||||
tracerx.Printf("http: client cert for %s", host)
|
|
||||||
tr.TLSClientConfig.Certificates = []tls.Certificate{getClientCertForHost(c, host)}
|
|
||||||
tr.TLSClientConfig.BuildNameToCertificate()
|
|
||||||
}
|
|
||||||
|
|
||||||
if isCertVerificationDisabledForHost(c, host) {
|
|
||||||
tr.TLSClientConfig.InsecureSkipVerify = true
|
|
||||||
} else {
|
|
||||||
tr.TLSClientConfig.RootCAs = getRootCAsForHost(c, host)
|
|
||||||
}
|
|
||||||
|
|
||||||
httpClient := &http.Client{
|
|
||||||
Transport: tr,
|
|
||||||
CheckRedirect: func(*http.Request, []*http.Request) error {
|
|
||||||
return http.ErrUseLastResponse
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
c.hostClients[host] = httpClient
|
|
||||||
if c.VerboseOut == nil {
|
|
||||||
c.VerboseOut = os.Stderr
|
|
||||||
}
|
|
||||||
|
|
||||||
return httpClient
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Client) CurrentUser() (string, string) {
|
|
||||||
userName, _ := c.gitEnv.Get("user.name")
|
|
||||||
userEmail, _ := c.gitEnv.Get("user.email")
|
|
||||||
return userName, userEmail
|
|
||||||
}
|
|
||||||
|
|
||||||
func newRequestForRetry(req *http.Request, location string) (*http.Request, error) {
|
|
||||||
newReq, err := http.NewRequest(req.Method, location, nil)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if req.URL.Scheme == "https" && newReq.URL.Scheme == "http" {
|
|
||||||
return nil, errors.New("lfsapi/client: refusing insecure redirect, https->http")
|
|
||||||
}
|
|
||||||
|
|
||||||
sameHost := req.URL.Host == newReq.URL.Host
|
|
||||||
for key := range req.Header {
|
|
||||||
if key == "Authorization" {
|
|
||||||
if !sameHost {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
}
|
|
||||||
newReq.Header.Set(key, req.Header.Get(key))
|
|
||||||
}
|
|
||||||
|
|
||||||
oldestURL := strings.SplitN(req.URL.String(), "?", 2)[0]
|
|
||||||
newURL := strings.SplitN(newReq.URL.String(), "?", 2)[0]
|
|
||||||
tracerx.Printf("api: redirect %s %s to %s", req.Method, oldestURL, newURL)
|
|
||||||
|
|
||||||
// This body will have already been rewound from a call to
|
|
||||||
// lfsapi.Client.traceRequest().
|
|
||||||
newReq.Body = req.Body
|
|
||||||
newReq.ContentLength = req.ContentLength
|
|
||||||
|
|
||||||
// Copy the request's context.Context, if any.
|
|
||||||
newReq = newReq.WithContext(req.Context())
|
|
||||||
|
|
||||||
return newReq, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
type deadlineConn struct {
|
|
||||||
Timeout time.Duration
|
|
||||||
net.Conn
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *deadlineConn) Read(b []byte) (int, error) {
|
|
||||||
if err := c.Conn.SetDeadline(time.Now().Add(c.Timeout)); err != nil {
|
|
||||||
return 0, err
|
|
||||||
}
|
|
||||||
return c.Conn.Read(b)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *deadlineConn) Write(b []byte) (int, error) {
|
|
||||||
if err := c.Conn.SetDeadline(time.Now().Add(c.Timeout)); err != nil {
|
|
||||||
return 0, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return c.Conn.Write(b)
|
|
||||||
}
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
UserAgent = config.VersionDesc
|
|
||||||
}
|
}
|
||||||
|
@ -49,7 +49,7 @@ func (c *Client) getCredentialHelper(u *url.URL) (CredentialHelper, Creds) {
|
|||||||
if u.User != nil && u.User.Username() != "" {
|
if u.User != nil && u.User.Username() != "" {
|
||||||
input["username"] = u.User.Username()
|
input["username"] = u.User.Username()
|
||||||
}
|
}
|
||||||
if c.uc.Bool("credential", rawurl, "usehttppath", false) {
|
if c.client.URLConfig().Bool("credential", rawurl, "usehttppath", false) {
|
||||||
input["path"] = strings.TrimPrefix(u.Path, "/")
|
input["path"] = strings.TrimPrefix(u.Path, "/")
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -62,7 +62,7 @@ func (c *Client) getCredentialHelper(u *url.URL) (CredentialHelper, Creds) {
|
|||||||
helpers = append(helpers, c.cachingCredHelper)
|
helpers = append(helpers, c.cachingCredHelper)
|
||||||
}
|
}
|
||||||
if c.askpassCredHelper != nil {
|
if c.askpassCredHelper != nil {
|
||||||
helper, _ := c.uc.Get("credential", rawurl, "helper")
|
helper, _ := c.client.URLConfig().Get("credential", rawurl, "helper")
|
||||||
if len(helper) == 0 {
|
if len(helper) == 0 {
|
||||||
helpers = append(helpers, c.askpassCredHelper)
|
helpers = append(helpers, c.askpassCredHelper)
|
||||||
}
|
}
|
||||||
|
@ -10,6 +10,7 @@ import (
|
|||||||
|
|
||||||
"github.com/git-lfs/git-lfs/config"
|
"github.com/git-lfs/git-lfs/config"
|
||||||
"github.com/git-lfs/git-lfs/git"
|
"github.com/git-lfs/git-lfs/git"
|
||||||
|
"github.com/git-lfs/git-lfs/lfshttp"
|
||||||
"github.com/rubyist/tracerx"
|
"github.com/rubyist/tracerx"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -26,10 +27,10 @@ const (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type EndpointFinder interface {
|
type EndpointFinder interface {
|
||||||
NewEndpointFromCloneURL(rawurl string) Endpoint
|
NewEndpointFromCloneURL(rawurl string) lfshttp.Endpoint
|
||||||
NewEndpoint(rawurl string) Endpoint
|
NewEndpoint(rawurl string) lfshttp.Endpoint
|
||||||
Endpoint(operation, remote string) Endpoint
|
Endpoint(operation, remote string) lfshttp.Endpoint
|
||||||
RemoteEndpoint(operation, remote string) Endpoint
|
RemoteEndpoint(operation, remote string) lfshttp.Endpoint
|
||||||
GitRemoteURL(remote string, forpush bool) string
|
GitRemoteURL(remote string, forpush bool) string
|
||||||
AccessFor(rawurl string) Access
|
AccessFor(rawurl string) Access
|
||||||
SetAccess(rawurl string, access Access)
|
SetAccess(rawurl string, access Access)
|
||||||
@ -49,9 +50,9 @@ type endpointGitFinder struct {
|
|||||||
urlConfig *config.URLConfig
|
urlConfig *config.URLConfig
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewEndpointFinder(ctx Context) EndpointFinder {
|
func NewEndpointFinder(ctx lfshttp.Context) EndpointFinder {
|
||||||
if ctx == nil {
|
if ctx == nil {
|
||||||
ctx = NewContext(nil, nil, nil)
|
ctx = lfshttp.NewContext(nil, nil, nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
e := &endpointGitFinder{
|
e := &endpointGitFinder{
|
||||||
@ -71,15 +72,15 @@ func NewEndpointFinder(ctx Context) EndpointFinder {
|
|||||||
return e
|
return e
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e *endpointGitFinder) Endpoint(operation, remote string) Endpoint {
|
func (e *endpointGitFinder) Endpoint(operation, remote string) lfshttp.Endpoint {
|
||||||
ep := e.getEndpoint(operation, remote)
|
ep := e.getEndpoint(operation, remote)
|
||||||
ep.Operation = operation
|
ep.Operation = operation
|
||||||
return ep
|
return ep
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e *endpointGitFinder) getEndpoint(operation, remote string) Endpoint {
|
func (e *endpointGitFinder) getEndpoint(operation, remote string) lfshttp.Endpoint {
|
||||||
if e.gitEnv == nil {
|
if e.gitEnv == nil {
|
||||||
return Endpoint{}
|
return lfshttp.Endpoint{}
|
||||||
}
|
}
|
||||||
|
|
||||||
if operation == "upload" {
|
if operation == "upload" {
|
||||||
@ -101,9 +102,9 @@ func (e *endpointGitFinder) getEndpoint(operation, remote string) Endpoint {
|
|||||||
return e.RemoteEndpoint(operation, defaultRemote)
|
return e.RemoteEndpoint(operation, defaultRemote)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e *endpointGitFinder) RemoteEndpoint(operation, remote string) Endpoint {
|
func (e *endpointGitFinder) RemoteEndpoint(operation, remote string) lfshttp.Endpoint {
|
||||||
if e.gitEnv == nil {
|
if e.gitEnv == nil {
|
||||||
return Endpoint{}
|
return lfshttp.Endpoint{}
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(remote) == 0 {
|
if len(remote) == 0 {
|
||||||
@ -125,7 +126,7 @@ func (e *endpointGitFinder) RemoteEndpoint(operation, remote string) Endpoint {
|
|||||||
return e.NewEndpointFromCloneURL(url)
|
return e.NewEndpointFromCloneURL(url)
|
||||||
}
|
}
|
||||||
|
|
||||||
return Endpoint{}
|
return lfshttp.Endpoint{}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e *endpointGitFinder) GitRemoteURL(remote string, forpush bool) string {
|
func (e *endpointGitFinder) GitRemoteURL(remote string, forpush bool) string {
|
||||||
@ -148,9 +149,9 @@ func (e *endpointGitFinder) GitRemoteURL(remote string, forpush bool) string {
|
|||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e *endpointGitFinder) NewEndpointFromCloneURL(rawurl string) Endpoint {
|
func (e *endpointGitFinder) NewEndpointFromCloneURL(rawurl string) lfshttp.Endpoint {
|
||||||
ep := e.NewEndpoint(rawurl)
|
ep := e.NewEndpoint(rawurl)
|
||||||
if ep.Url == UrlUnknown {
|
if ep.Url == lfshttp.UrlUnknown {
|
||||||
return ep
|
return ep
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -168,36 +169,36 @@ func (e *endpointGitFinder) NewEndpointFromCloneURL(rawurl string) Endpoint {
|
|||||||
return ep
|
return ep
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e *endpointGitFinder) NewEndpoint(rawurl string) Endpoint {
|
func (e *endpointGitFinder) NewEndpoint(rawurl string) lfshttp.Endpoint {
|
||||||
rawurl = e.ReplaceUrlAlias(rawurl)
|
rawurl = e.ReplaceUrlAlias(rawurl)
|
||||||
if strings.HasPrefix(rawurl, "/") {
|
if strings.HasPrefix(rawurl, "/") {
|
||||||
return endpointFromLocalPath(rawurl)
|
return lfshttp.EndpointFromLocalPath(rawurl)
|
||||||
}
|
}
|
||||||
u, err := url.Parse(rawurl)
|
u, err := url.Parse(rawurl)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return endpointFromBareSshUrl(rawurl)
|
return lfshttp.EndpointFromBareSshUrl(rawurl)
|
||||||
}
|
}
|
||||||
|
|
||||||
switch u.Scheme {
|
switch u.Scheme {
|
||||||
case "ssh":
|
case "ssh":
|
||||||
return endpointFromSshUrl(u)
|
return lfshttp.EndpointFromSshUrl(u)
|
||||||
case "http", "https":
|
case "http", "https":
|
||||||
return endpointFromHttpUrl(u)
|
return lfshttp.EndpointFromHttpUrl(u)
|
||||||
case "git":
|
case "git":
|
||||||
return endpointFromGitUrl(u, e)
|
return endpointFromGitUrl(u, e)
|
||||||
case "":
|
case "":
|
||||||
return endpointFromBareSshUrl(u.String())
|
return lfshttp.EndpointFromBareSshUrl(u.String())
|
||||||
default:
|
default:
|
||||||
if strings.HasPrefix(rawurl, u.Scheme+"::") {
|
if strings.HasPrefix(rawurl, u.Scheme+"::") {
|
||||||
// Looks like a remote helper; just pass it through.
|
// Looks like a remote helper; just pass it through.
|
||||||
return Endpoint{Url: rawurl}
|
return lfshttp.Endpoint{Url: rawurl}
|
||||||
}
|
}
|
||||||
// We probably got here because the "scheme" that was parsed is
|
// We probably got here because the "scheme" that was parsed is
|
||||||
// a hostname (whether FQDN or single word) and the URL parser
|
// a hostname (whether FQDN or single word) and the URL parser
|
||||||
// didn't know what to do with it. Do what Git does and treat
|
// didn't know what to do with it. Do what Git does and treat
|
||||||
// it as an SSH URL. This ensures we handle SSH config aliases
|
// it as an SSH URL. This ensures we handle SSH config aliases
|
||||||
// properly.
|
// properly.
|
||||||
return endpointFromBareSshUrl(u.String())
|
return lfshttp.EndpointFromBareSshUrl(u.String())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -305,3 +306,8 @@ func initAliases(e *endpointGitFinder, git config.Environment) {
|
|||||||
e.aliases[gitval[len(gitval)-1]] = gitkey[len(prefix) : len(gitkey)-len(suffix)]
|
e.aliases[gitval[len(gitval)-1]] = gitkey[len(prefix) : len(gitkey)-len(suffix)]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func endpointFromGitUrl(u *url.URL, e *endpointGitFinder) lfshttp.Endpoint {
|
||||||
|
u.Scheme = e.gitProtocol
|
||||||
|
return lfshttp.Endpoint{Url: u.String()}
|
||||||
|
}
|
||||||
|
@ -3,11 +3,12 @@ package lfsapi
|
|||||||
import (
|
import (
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/git-lfs/git-lfs/lfshttp"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestEndpointDefaultsToOrigin(t *testing.T) {
|
func TestEndpointDefaultsToOrigin(t *testing.T) {
|
||||||
finder := NewEndpointFinder(NewContext(nil, nil, map[string]string{
|
finder := NewEndpointFinder(lfshttp.NewContext(nil, nil, map[string]string{
|
||||||
"remote.origin.lfsurl": "abc",
|
"remote.origin.lfsurl": "abc",
|
||||||
}))
|
}))
|
||||||
|
|
||||||
@ -18,7 +19,7 @@ func TestEndpointDefaultsToOrigin(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestEndpointOverridesOrigin(t *testing.T) {
|
func TestEndpointOverridesOrigin(t *testing.T) {
|
||||||
finder := NewEndpointFinder(NewContext(nil, nil, map[string]string{
|
finder := NewEndpointFinder(lfshttp.NewContext(nil, nil, map[string]string{
|
||||||
"lfs.url": "abc",
|
"lfs.url": "abc",
|
||||||
"remote.origin.lfsurl": "def",
|
"remote.origin.lfsurl": "def",
|
||||||
}))
|
}))
|
||||||
@ -30,7 +31,7 @@ func TestEndpointOverridesOrigin(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestEndpointNoOverrideDefaultRemote(t *testing.T) {
|
func TestEndpointNoOverrideDefaultRemote(t *testing.T) {
|
||||||
finder := NewEndpointFinder(NewContext(nil, nil, map[string]string{
|
finder := NewEndpointFinder(lfshttp.NewContext(nil, nil, map[string]string{
|
||||||
"remote.origin.lfsurl": "abc",
|
"remote.origin.lfsurl": "abc",
|
||||||
"remote.other.lfsurl": "def",
|
"remote.other.lfsurl": "def",
|
||||||
}))
|
}))
|
||||||
@ -42,7 +43,7 @@ func TestEndpointNoOverrideDefaultRemote(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestEndpointUseAlternateRemote(t *testing.T) {
|
func TestEndpointUseAlternateRemote(t *testing.T) {
|
||||||
finder := NewEndpointFinder(NewContext(nil, nil, map[string]string{
|
finder := NewEndpointFinder(lfshttp.NewContext(nil, nil, map[string]string{
|
||||||
"remote.origin.lfsurl": "abc",
|
"remote.origin.lfsurl": "abc",
|
||||||
"remote.other.lfsurl": "def",
|
"remote.other.lfsurl": "def",
|
||||||
}))
|
}))
|
||||||
@ -54,7 +55,7 @@ func TestEndpointUseAlternateRemote(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestEndpointAddsLfsSuffix(t *testing.T) {
|
func TestEndpointAddsLfsSuffix(t *testing.T) {
|
||||||
finder := NewEndpointFinder(NewContext(nil, nil, map[string]string{
|
finder := NewEndpointFinder(lfshttp.NewContext(nil, nil, map[string]string{
|
||||||
"remote.origin.url": "https://example.com/foo/bar",
|
"remote.origin.url": "https://example.com/foo/bar",
|
||||||
}))
|
}))
|
||||||
|
|
||||||
@ -65,7 +66,7 @@ func TestEndpointAddsLfsSuffix(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestBareEndpointAddsLfsSuffix(t *testing.T) {
|
func TestBareEndpointAddsLfsSuffix(t *testing.T) {
|
||||||
finder := NewEndpointFinder(NewContext(nil, nil, map[string]string{
|
finder := NewEndpointFinder(lfshttp.NewContext(nil, nil, map[string]string{
|
||||||
"remote.origin.url": "https://example.com/foo/bar.git",
|
"remote.origin.url": "https://example.com/foo/bar.git",
|
||||||
}))
|
}))
|
||||||
|
|
||||||
@ -76,7 +77,7 @@ func TestBareEndpointAddsLfsSuffix(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestEndpointSeparateClonePushUrl(t *testing.T) {
|
func TestEndpointSeparateClonePushUrl(t *testing.T) {
|
||||||
finder := NewEndpointFinder(NewContext(nil, nil, map[string]string{
|
finder := NewEndpointFinder(lfshttp.NewContext(nil, nil, map[string]string{
|
||||||
"remote.origin.url": "https://example.com/foo/bar.git",
|
"remote.origin.url": "https://example.com/foo/bar.git",
|
||||||
"remote.origin.pushurl": "https://readwrite.com/foo/bar.git",
|
"remote.origin.pushurl": "https://readwrite.com/foo/bar.git",
|
||||||
}))
|
}))
|
||||||
@ -93,7 +94,7 @@ func TestEndpointSeparateClonePushUrl(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestEndpointOverriddenSeparateClonePushLfsUrl(t *testing.T) {
|
func TestEndpointOverriddenSeparateClonePushLfsUrl(t *testing.T) {
|
||||||
finder := NewEndpointFinder(NewContext(nil, nil, map[string]string{
|
finder := NewEndpointFinder(lfshttp.NewContext(nil, nil, map[string]string{
|
||||||
"remote.origin.url": "https://example.com/foo/bar.git",
|
"remote.origin.url": "https://example.com/foo/bar.git",
|
||||||
"remote.origin.pushurl": "https://readwrite.com/foo/bar.git",
|
"remote.origin.pushurl": "https://readwrite.com/foo/bar.git",
|
||||||
"remote.origin.lfsurl": "https://examplelfs.com/foo/bar",
|
"remote.origin.lfsurl": "https://examplelfs.com/foo/bar",
|
||||||
@ -112,7 +113,7 @@ func TestEndpointOverriddenSeparateClonePushLfsUrl(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestEndpointGlobalSeparateLfsPush(t *testing.T) {
|
func TestEndpointGlobalSeparateLfsPush(t *testing.T) {
|
||||||
finder := NewEndpointFinder(NewContext(nil, nil, map[string]string{
|
finder := NewEndpointFinder(lfshttp.NewContext(nil, nil, map[string]string{
|
||||||
"lfs.url": "https://readonly.com/foo/bar",
|
"lfs.url": "https://readonly.com/foo/bar",
|
||||||
"lfs.pushurl": "https://write.com/foo/bar",
|
"lfs.pushurl": "https://write.com/foo/bar",
|
||||||
}))
|
}))
|
||||||
@ -129,7 +130,7 @@ func TestEndpointGlobalSeparateLfsPush(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestSSHEndpointOverridden(t *testing.T) {
|
func TestSSHEndpointOverridden(t *testing.T) {
|
||||||
finder := NewEndpointFinder(NewContext(nil, nil, map[string]string{
|
finder := NewEndpointFinder(lfshttp.NewContext(nil, nil, map[string]string{
|
||||||
"remote.origin.url": "git@example.com:foo/bar",
|
"remote.origin.url": "git@example.com:foo/bar",
|
||||||
"remote.origin.lfsurl": "lfs",
|
"remote.origin.lfsurl": "lfs",
|
||||||
}))
|
}))
|
||||||
@ -142,7 +143,7 @@ func TestSSHEndpointOverridden(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestSSHEndpointAddsLfsSuffix(t *testing.T) {
|
func TestSSHEndpointAddsLfsSuffix(t *testing.T) {
|
||||||
finder := NewEndpointFinder(NewContext(nil, nil, map[string]string{
|
finder := NewEndpointFinder(lfshttp.NewContext(nil, nil, map[string]string{
|
||||||
"remote.origin.url": "ssh://git@example.com/foo/bar",
|
"remote.origin.url": "ssh://git@example.com/foo/bar",
|
||||||
}))
|
}))
|
||||||
|
|
||||||
@ -154,7 +155,7 @@ func TestSSHEndpointAddsLfsSuffix(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestSSHCustomPortEndpointAddsLfsSuffix(t *testing.T) {
|
func TestSSHCustomPortEndpointAddsLfsSuffix(t *testing.T) {
|
||||||
finder := NewEndpointFinder(NewContext(nil, nil, map[string]string{
|
finder := NewEndpointFinder(lfshttp.NewContext(nil, nil, map[string]string{
|
||||||
"remote.origin.url": "ssh://git@example.com:9000/foo/bar",
|
"remote.origin.url": "ssh://git@example.com:9000/foo/bar",
|
||||||
}))
|
}))
|
||||||
|
|
||||||
@ -166,7 +167,7 @@ func TestSSHCustomPortEndpointAddsLfsSuffix(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestBareSSHEndpointAddsLfsSuffix(t *testing.T) {
|
func TestBareSSHEndpointAddsLfsSuffix(t *testing.T) {
|
||||||
finder := NewEndpointFinder(NewContext(nil, nil, map[string]string{
|
finder := NewEndpointFinder(lfshttp.NewContext(nil, nil, map[string]string{
|
||||||
"remote.origin.url": "git@example.com:foo/bar.git",
|
"remote.origin.url": "git@example.com:foo/bar.git",
|
||||||
}))
|
}))
|
||||||
|
|
||||||
@ -178,7 +179,7 @@ func TestBareSSHEndpointAddsLfsSuffix(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestBareSSSHEndpointWithCustomPortInBrackets(t *testing.T) {
|
func TestBareSSSHEndpointWithCustomPortInBrackets(t *testing.T) {
|
||||||
finder := NewEndpointFinder(NewContext(nil, nil, map[string]string{
|
finder := NewEndpointFinder(lfshttp.NewContext(nil, nil, map[string]string{
|
||||||
"remote.origin.url": "[git@example.com:2222]:foo/bar.git",
|
"remote.origin.url": "[git@example.com:2222]:foo/bar.git",
|
||||||
}))
|
}))
|
||||||
|
|
||||||
@ -190,7 +191,7 @@ func TestBareSSSHEndpointWithCustomPortInBrackets(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestSSHEndpointFromGlobalLfsUrl(t *testing.T) {
|
func TestSSHEndpointFromGlobalLfsUrl(t *testing.T) {
|
||||||
finder := NewEndpointFinder(NewContext(nil, nil, map[string]string{
|
finder := NewEndpointFinder(lfshttp.NewContext(nil, nil, map[string]string{
|
||||||
"lfs.url": "git@example.com:foo/bar.git",
|
"lfs.url": "git@example.com:foo/bar.git",
|
||||||
}))
|
}))
|
||||||
|
|
||||||
@ -202,7 +203,7 @@ func TestSSHEndpointFromGlobalLfsUrl(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestHTTPEndpointAddsLfsSuffix(t *testing.T) {
|
func TestHTTPEndpointAddsLfsSuffix(t *testing.T) {
|
||||||
finder := NewEndpointFinder(NewContext(nil, nil, map[string]string{
|
finder := NewEndpointFinder(lfshttp.NewContext(nil, nil, map[string]string{
|
||||||
"remote.origin.url": "http://example.com/foo/bar",
|
"remote.origin.url": "http://example.com/foo/bar",
|
||||||
}))
|
}))
|
||||||
|
|
||||||
@ -214,7 +215,7 @@ func TestHTTPEndpointAddsLfsSuffix(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestBareHTTPEndpointAddsLfsSuffix(t *testing.T) {
|
func TestBareHTTPEndpointAddsLfsSuffix(t *testing.T) {
|
||||||
finder := NewEndpointFinder(NewContext(nil, nil, map[string]string{
|
finder := NewEndpointFinder(lfshttp.NewContext(nil, nil, map[string]string{
|
||||||
"remote.origin.url": "http://example.com/foo/bar.git",
|
"remote.origin.url": "http://example.com/foo/bar.git",
|
||||||
}))
|
}))
|
||||||
|
|
||||||
@ -226,7 +227,7 @@ func TestBareHTTPEndpointAddsLfsSuffix(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestGitEndpointAddsLfsSuffix(t *testing.T) {
|
func TestGitEndpointAddsLfsSuffix(t *testing.T) {
|
||||||
finder := NewEndpointFinder(NewContext(nil, nil, map[string]string{
|
finder := NewEndpointFinder(lfshttp.NewContext(nil, nil, map[string]string{
|
||||||
"remote.origin.url": "git://example.com/foo/bar",
|
"remote.origin.url": "git://example.com/foo/bar",
|
||||||
}))
|
}))
|
||||||
|
|
||||||
@ -238,7 +239,7 @@ func TestGitEndpointAddsLfsSuffix(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestGitEndpointAddsLfsSuffixWithCustomProtocol(t *testing.T) {
|
func TestGitEndpointAddsLfsSuffixWithCustomProtocol(t *testing.T) {
|
||||||
finder := NewEndpointFinder(NewContext(nil, nil, map[string]string{
|
finder := NewEndpointFinder(lfshttp.NewContext(nil, nil, map[string]string{
|
||||||
"remote.origin.url": "git://example.com/foo/bar",
|
"remote.origin.url": "git://example.com/foo/bar",
|
||||||
"lfs.gitprotocol": "http",
|
"lfs.gitprotocol": "http",
|
||||||
}))
|
}))
|
||||||
@ -251,7 +252,7 @@ func TestGitEndpointAddsLfsSuffixWithCustomProtocol(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestBareGitEndpointAddsLfsSuffix(t *testing.T) {
|
func TestBareGitEndpointAddsLfsSuffix(t *testing.T) {
|
||||||
finder := NewEndpointFinder(NewContext(nil, nil, map[string]string{
|
finder := NewEndpointFinder(lfshttp.NewContext(nil, nil, map[string]string{
|
||||||
"remote.origin.url": "git://example.com/foo/bar.git",
|
"remote.origin.url": "git://example.com/foo/bar.git",
|
||||||
}))
|
}))
|
||||||
|
|
||||||
@ -263,7 +264,7 @@ func TestBareGitEndpointAddsLfsSuffix(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestLocalPathEndpointAddsDotGitDir(t *testing.T) {
|
func TestLocalPathEndpointAddsDotGitDir(t *testing.T) {
|
||||||
finder := NewEndpointFinder(NewContext(nil, nil, map[string]string{
|
finder := NewEndpointFinder(lfshttp.NewContext(nil, nil, map[string]string{
|
||||||
"remote.origin.url": "/local/path",
|
"remote.origin.url": "/local/path",
|
||||||
}))
|
}))
|
||||||
e := finder.Endpoint("download", "")
|
e := finder.Endpoint("download", "")
|
||||||
@ -271,7 +272,7 @@ func TestLocalPathEndpointAddsDotGitDir(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestLocalPathEndpointPreservesDotGit(t *testing.T) {
|
func TestLocalPathEndpointPreservesDotGit(t *testing.T) {
|
||||||
finder := NewEndpointFinder(NewContext(nil, nil, map[string]string{
|
finder := NewEndpointFinder(lfshttp.NewContext(nil, nil, map[string]string{
|
||||||
"remote.origin.url": "/local/path.git",
|
"remote.origin.url": "/local/path.git",
|
||||||
}))
|
}))
|
||||||
e := finder.Endpoint("download", "")
|
e := finder.Endpoint("download", "")
|
||||||
@ -294,7 +295,7 @@ func TestAccessConfig(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for value, expected := range tests {
|
for value, expected := range tests {
|
||||||
finder := NewEndpointFinder(NewContext(nil, nil, map[string]string{
|
finder := NewEndpointFinder(lfshttp.NewContext(nil, nil, map[string]string{
|
||||||
"lfs.url": "http://example.com",
|
"lfs.url": "http://example.com",
|
||||||
"lfs.http://example.com.access": value,
|
"lfs.http://example.com.access": value,
|
||||||
"lfs.https://example.com.access": "bad",
|
"lfs.https://example.com.access": "bad",
|
||||||
@ -313,7 +314,7 @@ func TestAccessConfig(t *testing.T) {
|
|||||||
|
|
||||||
// Test again but with separate push url
|
// Test again but with separate push url
|
||||||
for value, expected := range tests {
|
for value, expected := range tests {
|
||||||
finder := NewEndpointFinder(NewContext(nil, nil, map[string]string{
|
finder := NewEndpointFinder(lfshttp.NewContext(nil, nil, map[string]string{
|
||||||
"lfs.url": "http://example.com",
|
"lfs.url": "http://example.com",
|
||||||
"lfs.pushurl": "http://examplepush.com",
|
"lfs.pushurl": "http://examplepush.com",
|
||||||
"lfs.http://example.com.access": value,
|
"lfs.http://example.com.access": value,
|
||||||
@ -340,7 +341,7 @@ func TestAccessAbsentConfig(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestSetAccess(t *testing.T) {
|
func TestSetAccess(t *testing.T) {
|
||||||
finder := NewEndpointFinder(NewContext(nil, nil, map[string]string{}))
|
finder := NewEndpointFinder(lfshttp.NewContext(nil, nil, map[string]string{}))
|
||||||
|
|
||||||
assert.Equal(t, NoneAccess, finder.AccessFor("http://example.com"))
|
assert.Equal(t, NoneAccess, finder.AccessFor("http://example.com"))
|
||||||
finder.SetAccess("http://example.com", NTLMAccess)
|
finder.SetAccess("http://example.com", NTLMAccess)
|
||||||
@ -348,7 +349,7 @@ func TestSetAccess(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestChangeAccess(t *testing.T) {
|
func TestChangeAccess(t *testing.T) {
|
||||||
finder := NewEndpointFinder(NewContext(nil, nil, map[string]string{
|
finder := NewEndpointFinder(lfshttp.NewContext(nil, nil, map[string]string{
|
||||||
"lfs.http://example.com.access": "basic",
|
"lfs.http://example.com.access": "basic",
|
||||||
}))
|
}))
|
||||||
|
|
||||||
@ -358,7 +359,7 @@ func TestChangeAccess(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestDeleteAccessWithNone(t *testing.T) {
|
func TestDeleteAccessWithNone(t *testing.T) {
|
||||||
finder := NewEndpointFinder(NewContext(nil, nil, map[string]string{
|
finder := NewEndpointFinder(lfshttp.NewContext(nil, nil, map[string]string{
|
||||||
"lfs.http://example.com.access": "basic",
|
"lfs.http://example.com.access": "basic",
|
||||||
}))
|
}))
|
||||||
|
|
||||||
@ -368,7 +369,7 @@ func TestDeleteAccessWithNone(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestDeleteAccessWithEmptyString(t *testing.T) {
|
func TestDeleteAccessWithEmptyString(t *testing.T) {
|
||||||
finder := NewEndpointFinder(NewContext(nil, nil, map[string]string{
|
finder := NewEndpointFinder(lfshttp.NewContext(nil, nil, map[string]string{
|
||||||
"lfs.http://example.com.access": "basic",
|
"lfs.http://example.com.access": "basic",
|
||||||
}))
|
}))
|
||||||
|
|
||||||
@ -379,11 +380,11 @@ func TestDeleteAccessWithEmptyString(t *testing.T) {
|
|||||||
|
|
||||||
type EndpointParsingTestCase struct {
|
type EndpointParsingTestCase struct {
|
||||||
Given string
|
Given string
|
||||||
Expected Endpoint
|
Expected lfshttp.Endpoint
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *EndpointParsingTestCase) Assert(t *testing.T) {
|
func (c *EndpointParsingTestCase) Assert(t *testing.T) {
|
||||||
finder := NewEndpointFinder(NewContext(nil, nil, map[string]string{
|
finder := NewEndpointFinder(lfshttp.NewContext(nil, nil, map[string]string{
|
||||||
"url.https://github.com/.insteadof": "gh:",
|
"url.https://github.com/.insteadof": "gh:",
|
||||||
}))
|
}))
|
||||||
actual := finder.NewEndpoint(c.Given)
|
actual := finder.NewEndpoint(c.Given)
|
||||||
@ -396,7 +397,7 @@ func TestEndpointParsing(t *testing.T) {
|
|||||||
for desc, c := range map[string]EndpointParsingTestCase{
|
for desc, c := range map[string]EndpointParsingTestCase{
|
||||||
"simple bare ssh": {
|
"simple bare ssh": {
|
||||||
"git@github.com:git-lfs/git-lfs.git",
|
"git@github.com:git-lfs/git-lfs.git",
|
||||||
Endpoint{
|
lfshttp.Endpoint{
|
||||||
Url: "https://github.com/git-lfs/git-lfs.git",
|
Url: "https://github.com/git-lfs/git-lfs.git",
|
||||||
SshUserAndHost: "git@github.com",
|
SshUserAndHost: "git@github.com",
|
||||||
SshPath: "git-lfs/git-lfs.git",
|
SshPath: "git-lfs/git-lfs.git",
|
||||||
@ -406,7 +407,7 @@ func TestEndpointParsing(t *testing.T) {
|
|||||||
},
|
},
|
||||||
"port bare ssh": {
|
"port bare ssh": {
|
||||||
"[git@ssh.github.com:443]:git-lfs/git-lfs.git",
|
"[git@ssh.github.com:443]:git-lfs/git-lfs.git",
|
||||||
Endpoint{
|
lfshttp.Endpoint{
|
||||||
Url: "https://ssh.github.com/git-lfs/git-lfs.git",
|
Url: "https://ssh.github.com/git-lfs/git-lfs.git",
|
||||||
SshUserAndHost: "git@ssh.github.com",
|
SshUserAndHost: "git@ssh.github.com",
|
||||||
SshPath: "git-lfs/git-lfs.git",
|
SshPath: "git-lfs/git-lfs.git",
|
||||||
@ -416,7 +417,7 @@ func TestEndpointParsing(t *testing.T) {
|
|||||||
},
|
},
|
||||||
"no user bare ssh": {
|
"no user bare ssh": {
|
||||||
"github.com:git-lfs/git-lfs.git",
|
"github.com:git-lfs/git-lfs.git",
|
||||||
Endpoint{
|
lfshttp.Endpoint{
|
||||||
Url: "https://github.com/git-lfs/git-lfs.git",
|
Url: "https://github.com/git-lfs/git-lfs.git",
|
||||||
SshUserAndHost: "github.com",
|
SshUserAndHost: "github.com",
|
||||||
SshPath: "git-lfs/git-lfs.git",
|
SshPath: "git-lfs/git-lfs.git",
|
||||||
@ -426,7 +427,7 @@ func TestEndpointParsing(t *testing.T) {
|
|||||||
},
|
},
|
||||||
"bare word bare ssh": {
|
"bare word bare ssh": {
|
||||||
"github:git-lfs/git-lfs.git",
|
"github:git-lfs/git-lfs.git",
|
||||||
Endpoint{
|
lfshttp.Endpoint{
|
||||||
Url: "https://github/git-lfs/git-lfs.git",
|
Url: "https://github/git-lfs/git-lfs.git",
|
||||||
SshUserAndHost: "github",
|
SshUserAndHost: "github",
|
||||||
SshPath: "git-lfs/git-lfs.git",
|
SshPath: "git-lfs/git-lfs.git",
|
||||||
@ -436,7 +437,7 @@ func TestEndpointParsing(t *testing.T) {
|
|||||||
},
|
},
|
||||||
"insteadof alias": {
|
"insteadof alias": {
|
||||||
"gh:git-lfs/git-lfs.git",
|
"gh:git-lfs/git-lfs.git",
|
||||||
Endpoint{
|
lfshttp.Endpoint{
|
||||||
Url: "https://github.com/git-lfs/git-lfs.git",
|
Url: "https://github.com/git-lfs/git-lfs.git",
|
||||||
SshUserAndHost: "",
|
SshUserAndHost: "",
|
||||||
SshPath: "",
|
SshPath: "",
|
||||||
@ -446,7 +447,7 @@ func TestEndpointParsing(t *testing.T) {
|
|||||||
},
|
},
|
||||||
"remote helper": {
|
"remote helper": {
|
||||||
"remote::git-lfs/git-lfs.git",
|
"remote::git-lfs/git-lfs.git",
|
||||||
Endpoint{
|
lfshttp.Endpoint{
|
||||||
Url: "remote::git-lfs/git-lfs.git",
|
Url: "remote::git-lfs/git-lfs.git",
|
||||||
SshUserAndHost: "",
|
SshUserAndHost: "",
|
||||||
SshPath: "",
|
SshPath: "",
|
||||||
@ -458,3 +459,21 @@ func TestEndpointParsing(t *testing.T) {
|
|||||||
t.Run(desc, c.Assert)
|
t.Run(desc, c.Assert)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestNewEndpointFromCloneURLWithConfig(t *testing.T) {
|
||||||
|
expected := "https://foo/bar.git/info/lfs"
|
||||||
|
tests := []string{
|
||||||
|
"https://foo/bar",
|
||||||
|
"https://foo/bar/",
|
||||||
|
"https://foo/bar.git",
|
||||||
|
"https://foo/bar.git/",
|
||||||
|
}
|
||||||
|
|
||||||
|
finder := NewEndpointFinder(nil)
|
||||||
|
for _, actual := range tests {
|
||||||
|
e := finder.NewEndpointFromCloneURL(actual)
|
||||||
|
if e.Url != expected {
|
||||||
|
t.Errorf("%s returned bad endpoint url %s", actual, e.Url)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -1,23 +0,0 @@
|
|||||||
package lfsapi
|
|
||||||
|
|
||||||
import (
|
|
||||||
"testing"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestNewEndpointFromCloneURLWithConfig(t *testing.T) {
|
|
||||||
expected := "https://foo/bar.git/info/lfs"
|
|
||||||
tests := []string{
|
|
||||||
"https://foo/bar",
|
|
||||||
"https://foo/bar/",
|
|
||||||
"https://foo/bar.git",
|
|
||||||
"https://foo/bar.git/",
|
|
||||||
}
|
|
||||||
|
|
||||||
finder := NewEndpointFinder(nil)
|
|
||||||
for _, actual := range tests {
|
|
||||||
e := finder.NewEndpointFromCloneURL(actual)
|
|
||||||
if e.Url != expected {
|
|
||||||
t.Errorf("%s returned bad endpoint url %s", actual, e.Url)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
174
lfsapi/lfsapi.go
174
lfsapi/lfsapi.go
@ -1,69 +1,32 @@
|
|||||||
package lfsapi
|
package lfsapi
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
|
||||||
"net/http"
|
|
||||||
"regexp"
|
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
"github.com/ThomsonReutersEikon/go-ntlm/ntlm"
|
"github.com/ThomsonReutersEikon/go-ntlm/ntlm"
|
||||||
"github.com/git-lfs/git-lfs/config"
|
|
||||||
"github.com/git-lfs/git-lfs/errors"
|
"github.com/git-lfs/git-lfs/errors"
|
||||||
"github.com/git-lfs/git-lfs/git"
|
"github.com/git-lfs/git-lfs/lfshttp"
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
lfsMediaTypeRE = regexp.MustCompile(`\Aapplication/vnd\.git\-lfs\+json(;|\z)`)
|
|
||||||
jsonMediaTypeRE = regexp.MustCompile(`\Aapplication/json(;|\z)`)
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type Client struct {
|
type Client struct {
|
||||||
Endpoints EndpointFinder
|
Endpoints EndpointFinder
|
||||||
Credentials CredentialHelper
|
Credentials CredentialHelper
|
||||||
SSH SSHResolver
|
|
||||||
Netrc NetrcFinder
|
Netrc NetrcFinder
|
||||||
|
|
||||||
DialTimeout int
|
|
||||||
KeepaliveTimeout int
|
|
||||||
TLSTimeout int
|
|
||||||
ConcurrentTransfers int
|
|
||||||
SkipSSLVerify bool
|
|
||||||
|
|
||||||
Verbose bool
|
|
||||||
DebuggingVerbose bool
|
|
||||||
VerboseOut io.Writer
|
|
||||||
|
|
||||||
hostClients map[string]*http.Client
|
|
||||||
clientMu sync.Mutex
|
|
||||||
|
|
||||||
ntlmSessions map[string]ntlm.ClientSession
|
ntlmSessions map[string]ntlm.ClientSession
|
||||||
ntlmMu sync.Mutex
|
ntlmMu sync.Mutex
|
||||||
|
|
||||||
httpLogger *syncLogger
|
|
||||||
|
|
||||||
LoggingStats bool // DEPRECATED
|
|
||||||
|
|
||||||
commandCredHelper *commandCredentialHelper
|
commandCredHelper *commandCredentialHelper
|
||||||
askpassCredHelper *AskPassCredentialHelper
|
askpassCredHelper *AskPassCredentialHelper
|
||||||
cachingCredHelper *credentialCacher
|
cachingCredHelper *credentialCacher
|
||||||
gitEnv config.Environment
|
|
||||||
osEnv config.Environment
|
|
||||||
uc *config.URLConfig
|
|
||||||
|
|
||||||
sshTries int
|
client *lfshttp.Client
|
||||||
}
|
}
|
||||||
|
|
||||||
type Context interface {
|
func NewClient(ctx lfshttp.Context) (*Client, error) {
|
||||||
GitConfig() *git.Configuration
|
|
||||||
OSEnv() config.Environment
|
|
||||||
GitEnv() config.Environment
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewClient(ctx Context) (*Client, error) {
|
|
||||||
if ctx == nil {
|
if ctx == nil {
|
||||||
ctx = NewContext(nil, nil, nil)
|
ctx = lfshttp.NewContext(nil, nil, nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
gitEnv := ctx.GitEnv()
|
gitEnv := ctx.GitEnv()
|
||||||
@ -73,30 +36,18 @@ func NewClient(ctx Context) (*Client, error) {
|
|||||||
return nil, errors.Wrap(err, fmt.Sprintf("bad netrc file %s", netrcfile))
|
return nil, errors.Wrap(err, fmt.Sprintf("bad netrc file %s", netrcfile))
|
||||||
}
|
}
|
||||||
|
|
||||||
cacheCreds := gitEnv.Bool("lfs.cachecredentials", true)
|
httpClient, err := lfshttp.NewClient(ctx)
|
||||||
var sshResolver SSHResolver = &sshAuthClient{os: osEnv, git: gitEnv}
|
if err != nil {
|
||||||
if cacheCreds {
|
return nil, errors.Wrap(err, fmt.Sprintf("error creating http client"))
|
||||||
sshResolver = withSSHCache(sshResolver)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
c := &Client{
|
c := &Client{
|
||||||
Endpoints: NewEndpointFinder(ctx),
|
Endpoints: NewEndpointFinder(ctx),
|
||||||
SSH: sshResolver,
|
|
||||||
Netrc: netrc,
|
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", 3),
|
|
||||||
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),
|
|
||||||
commandCredHelper: &commandCredentialHelper{
|
commandCredHelper: &commandCredentialHelper{
|
||||||
SkipPrompt: osEnv.Bool("GIT_TERMINAL_PROMPT", false),
|
SkipPrompt: osEnv.Bool("GIT_TERMINAL_PROMPT", false),
|
||||||
},
|
},
|
||||||
gitEnv: gitEnv,
|
client: httpClient,
|
||||||
osEnv: osEnv,
|
|
||||||
uc: config.NewURLConfig(gitEnv),
|
|
||||||
sshTries: gitEnv.Int("lfs.ssh.retries", 5),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
askpass, ok := osEnv.Get("GIT_ASKPASS")
|
askpass, ok := osEnv.Get("GIT_ASKPASS")
|
||||||
@ -112,117 +63,10 @@ func NewClient(ctx Context) (*Client, error) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
cacheCreds := gitEnv.Bool("lfs.cachecredentials", true)
|
||||||
if cacheCreds {
|
if cacheCreds {
|
||||||
c.cachingCredHelper = newCredentialCacher()
|
c.cachingCredHelper = newCredentialCacher()
|
||||||
}
|
}
|
||||||
|
|
||||||
return c, nil
|
return c, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Client) GitEnv() config.Environment {
|
|
||||||
return c.gitEnv
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Client) OSEnv() config.Environment {
|
|
||||||
return c.osEnv
|
|
||||||
}
|
|
||||||
|
|
||||||
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 testContext struct {
|
|
||||||
gitConfig *git.Configuration
|
|
||||||
osEnv config.Environment
|
|
||||||
gitEnv config.Environment
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *testContext) GitConfig() *git.Configuration {
|
|
||||||
return c.gitConfig
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *testContext) OSEnv() config.Environment {
|
|
||||||
return c.osEnv
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *testContext) GitEnv() config.Environment {
|
|
||||||
return c.gitEnv
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewContext(gitConf *git.Configuration, osEnv, gitEnv map[string]string) Context {
|
|
||||||
c := &testContext{gitConfig: gitConf}
|
|
||||||
if c.gitConfig == nil {
|
|
||||||
c.gitConfig = git.NewConfig("", "")
|
|
||||||
}
|
|
||||||
if osEnv != nil {
|
|
||||||
c.osEnv = testEnv(osEnv)
|
|
||||||
} else {
|
|
||||||
c.osEnv = make(testEnv)
|
|
||||||
}
|
|
||||||
|
|
||||||
if gitEnv != nil {
|
|
||||||
c.gitEnv = testEnv(gitEnv)
|
|
||||||
} else {
|
|
||||||
c.gitEnv = make(testEnv)
|
|
||||||
}
|
|
||||||
return c
|
|
||||||
}
|
|
||||||
|
|
||||||
type testEnv map[string]string
|
|
||||||
|
|
||||||
func (e testEnv) Get(key string) (v string, ok bool) {
|
|
||||||
v, ok = e[key]
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e testEnv) GetAll(key string) []string {
|
|
||||||
if v, ok := e.Get(key); ok {
|
|
||||||
return []string{v}
|
|
||||||
}
|
|
||||||
return make([]string, 0)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e testEnv) Int(key string, def int) int {
|
|
||||||
s, _ := e.Get(key)
|
|
||||||
return config.Int(s, def)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e testEnv) Bool(key string, def bool) bool {
|
|
||||||
s, _ := e.Get(key)
|
|
||||||
return config.Bool(s, def)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e testEnv) All() map[string][]string {
|
|
||||||
m := make(map[string][]string)
|
|
||||||
for k, _ := range e {
|
|
||||||
m[k] = e.GetAll(k)
|
|
||||||
}
|
|
||||||
return m
|
|
||||||
}
|
|
||||||
|
@ -11,6 +11,7 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/ThomsonReutersEikon/go-ntlm/ntlm"
|
"github.com/ThomsonReutersEikon/go-ntlm/ntlm"
|
||||||
|
"github.com/git-lfs/git-lfs/lfshttp"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
@ -94,7 +95,7 @@ func TestNtlmAuth(t *testing.T) {
|
|||||||
require.Nil(t, err)
|
require.Nil(t, err)
|
||||||
|
|
||||||
credHelper := newMockCredentialHelper()
|
credHelper := newMockCredentialHelper()
|
||||||
cli, err := NewClient(NewContext(nil, nil, map[string]string{
|
cli, err := NewClient(lfshttp.NewContext(nil, nil, map[string]string{
|
||||||
"lfs.url": srv.URL + "/ntlm",
|
"lfs.url": srv.URL + "/ntlm",
|
||||||
"lfs." + srv.URL + "/ntlm.access": "ntlm",
|
"lfs." + srv.URL + "/ntlm.access": "ntlm",
|
||||||
}))
|
}))
|
||||||
|
39
lfshttp/body.go
Normal file
39
lfshttp/body.go
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
package lfshttp
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/json"
|
||||||
|
"io"
|
||||||
|
"net/http"
|
||||||
|
"strconv"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ReadSeekCloser interface {
|
||||||
|
io.Seeker
|
||||||
|
io.ReadCloser
|
||||||
|
}
|
||||||
|
|
||||||
|
func MarshalToRequest(req *http.Request, obj interface{}) error {
|
||||||
|
by, err := json.Marshal(obj)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
clen := len(by)
|
||||||
|
req.Header.Set("Content-Length", strconv.Itoa(clen))
|
||||||
|
req.ContentLength = int64(clen)
|
||||||
|
req.Body = NewByteBody(by)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewByteBody(by []byte) ReadSeekCloser {
|
||||||
|
return &closingByteReader{Reader: bytes.NewReader(by)}
|
||||||
|
}
|
||||||
|
|
||||||
|
type closingByteReader struct {
|
||||||
|
*bytes.Reader
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *closingByteReader) Close() error {
|
||||||
|
return nil
|
||||||
|
}
|
@ -1,4 +1,4 @@
|
|||||||
package lfsapi
|
package lfshttp
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"crypto/tls"
|
"crypto/tls"
|
@ -1,4 +1,4 @@
|
|||||||
package lfsapi
|
package lfshttp
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"crypto/x509"
|
"crypto/x509"
|
@ -1,4 +1,4 @@
|
|||||||
package lfsapi
|
package lfshttp
|
||||||
|
|
||||||
import "crypto/x509"
|
import "crypto/x509"
|
||||||
|
|
@ -1,4 +1,4 @@
|
|||||||
package lfsapi
|
package lfshttp
|
||||||
|
|
||||||
import "crypto/x509"
|
import "crypto/x509"
|
||||||
|
|
@ -1,4 +1,4 @@
|
|||||||
package lfsapi
|
package lfshttp
|
||||||
|
|
||||||
import "crypto/x509"
|
import "crypto/x509"
|
||||||
|
|
@ -1,4 +1,4 @@
|
|||||||
package lfsapi
|
package lfshttp
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
@ -158,7 +158,7 @@ func TestCertFromSSLCAPathEnv(t *testing.T) {
|
|||||||
|
|
||||||
func TestCertVerifyDisabledGlobalEnv(t *testing.T) {
|
func TestCertVerifyDisabledGlobalEnv(t *testing.T) {
|
||||||
empty, _ := NewClient(nil)
|
empty, _ := NewClient(nil)
|
||||||
httpClient := empty.httpClient("anyhost.com")
|
httpClient := empty.HttpClient("anyhost.com")
|
||||||
tr, ok := httpClient.Transport.(*http.Transport)
|
tr, ok := httpClient.Transport.(*http.Transport)
|
||||||
if assert.True(t, ok) {
|
if assert.True(t, ok) {
|
||||||
assert.False(t, tr.TLSClientConfig.InsecureSkipVerify)
|
assert.False(t, tr.TLSClientConfig.InsecureSkipVerify)
|
||||||
@ -170,7 +170,7 @@ func TestCertVerifyDisabledGlobalEnv(t *testing.T) {
|
|||||||
|
|
||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
|
|
||||||
httpClient = c.httpClient("anyhost.com")
|
httpClient = c.HttpClient("anyhost.com")
|
||||||
tr, ok = httpClient.Transport.(*http.Transport)
|
tr, ok = httpClient.Transport.(*http.Transport)
|
||||||
if assert.True(t, ok) {
|
if assert.True(t, ok) {
|
||||||
assert.True(t, tr.TLSClientConfig.InsecureSkipVerify)
|
assert.True(t, tr.TLSClientConfig.InsecureSkipVerify)
|
||||||
@ -179,7 +179,7 @@ func TestCertVerifyDisabledGlobalEnv(t *testing.T) {
|
|||||||
|
|
||||||
func TestCertVerifyDisabledGlobalConfig(t *testing.T) {
|
func TestCertVerifyDisabledGlobalConfig(t *testing.T) {
|
||||||
def, _ := NewClient(nil)
|
def, _ := NewClient(nil)
|
||||||
httpClient := def.httpClient("anyhost.com")
|
httpClient := def.HttpClient("anyhost.com")
|
||||||
tr, ok := httpClient.Transport.(*http.Transport)
|
tr, ok := httpClient.Transport.(*http.Transport)
|
||||||
if assert.True(t, ok) {
|
if assert.True(t, ok) {
|
||||||
assert.False(t, tr.TLSClientConfig.InsecureSkipVerify)
|
assert.False(t, tr.TLSClientConfig.InsecureSkipVerify)
|
||||||
@ -190,7 +190,7 @@ func TestCertVerifyDisabledGlobalConfig(t *testing.T) {
|
|||||||
}))
|
}))
|
||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
|
|
||||||
httpClient = c.httpClient("anyhost.com")
|
httpClient = c.HttpClient("anyhost.com")
|
||||||
tr, ok = httpClient.Transport.(*http.Transport)
|
tr, ok = httpClient.Transport.(*http.Transport)
|
||||||
if assert.True(t, ok) {
|
if assert.True(t, ok) {
|
||||||
assert.True(t, tr.TLSClientConfig.InsecureSkipVerify)
|
assert.True(t, tr.TLSClientConfig.InsecureSkipVerify)
|
||||||
@ -199,13 +199,13 @@ func TestCertVerifyDisabledGlobalConfig(t *testing.T) {
|
|||||||
|
|
||||||
func TestCertVerifyDisabledHostConfig(t *testing.T) {
|
func TestCertVerifyDisabledHostConfig(t *testing.T) {
|
||||||
def, _ := NewClient(nil)
|
def, _ := NewClient(nil)
|
||||||
httpClient := def.httpClient("specifichost.com")
|
httpClient := def.HttpClient("specifichost.com")
|
||||||
tr, ok := httpClient.Transport.(*http.Transport)
|
tr, ok := httpClient.Transport.(*http.Transport)
|
||||||
if assert.True(t, ok) {
|
if assert.True(t, ok) {
|
||||||
assert.False(t, tr.TLSClientConfig.InsecureSkipVerify)
|
assert.False(t, tr.TLSClientConfig.InsecureSkipVerify)
|
||||||
}
|
}
|
||||||
|
|
||||||
httpClient = def.httpClient("otherhost.com")
|
httpClient = def.HttpClient("otherhost.com")
|
||||||
tr, ok = httpClient.Transport.(*http.Transport)
|
tr, ok = httpClient.Transport.(*http.Transport)
|
||||||
if assert.True(t, ok) {
|
if assert.True(t, ok) {
|
||||||
assert.False(t, tr.TLSClientConfig.InsecureSkipVerify)
|
assert.False(t, tr.TLSClientConfig.InsecureSkipVerify)
|
||||||
@ -216,13 +216,13 @@ func TestCertVerifyDisabledHostConfig(t *testing.T) {
|
|||||||
}))
|
}))
|
||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
|
|
||||||
httpClient = c.httpClient("specifichost.com")
|
httpClient = c.HttpClient("specifichost.com")
|
||||||
tr, ok = httpClient.Transport.(*http.Transport)
|
tr, ok = httpClient.Transport.(*http.Transport)
|
||||||
if assert.True(t, ok) {
|
if assert.True(t, ok) {
|
||||||
assert.True(t, tr.TLSClientConfig.InsecureSkipVerify)
|
assert.True(t, tr.TLSClientConfig.InsecureSkipVerify)
|
||||||
}
|
}
|
||||||
|
|
||||||
httpClient = c.httpClient("otherhost.com")
|
httpClient = c.HttpClient("otherhost.com")
|
||||||
tr, ok = httpClient.Transport.(*http.Transport)
|
tr, ok = httpClient.Transport.(*http.Transport)
|
||||||
if assert.True(t, ok) {
|
if assert.True(t, ok) {
|
||||||
assert.False(t, tr.TLSClientConfig.InsecureSkipVerify)
|
assert.False(t, tr.TLSClientConfig.InsecureSkipVerify)
|
@ -1,4 +1,4 @@
|
|||||||
package lfsapi
|
package lfshttp
|
||||||
|
|
||||||
import "crypto/x509"
|
import "crypto/x509"
|
||||||
|
|
539
lfshttp/client.go
Normal file
539
lfshttp/client.go
Normal file
@ -0,0 +1,539 @@
|
|||||||
|
package lfshttp
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"crypto/tls"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"net"
|
||||||
|
"net/http"
|
||||||
|
"net/textproto"
|
||||||
|
"net/url"
|
||||||
|
"os"
|
||||||
|
"regexp"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/git-lfs/git-lfs/config"
|
||||||
|
"github.com/git-lfs/git-lfs/errors"
|
||||||
|
"github.com/git-lfs/git-lfs/tools"
|
||||||
|
"github.com/rubyist/tracerx"
|
||||||
|
)
|
||||||
|
|
||||||
|
const MediaType = "application/vnd.git-lfs+json; charset=utf-8"
|
||||||
|
|
||||||
|
var (
|
||||||
|
UserAgent = "git-lfs"
|
||||||
|
httpRE = regexp.MustCompile(`\Ahttps?://`)
|
||||||
|
)
|
||||||
|
|
||||||
|
var hintFileUrl = strings.TrimSpace(`
|
||||||
|
hint: The remote resolves to a file:// URL, which can only work with a
|
||||||
|
hint: standalone transfer agent. See section "Using a Custom Transfer Type
|
||||||
|
hint: without the API server" in custom-transfers.md for details.
|
||||||
|
`)
|
||||||
|
|
||||||
|
type Client struct {
|
||||||
|
SSH SSHResolver
|
||||||
|
|
||||||
|
DialTimeout int
|
||||||
|
KeepaliveTimeout int
|
||||||
|
TLSTimeout int
|
||||||
|
ConcurrentTransfers int
|
||||||
|
SkipSSLVerify bool
|
||||||
|
|
||||||
|
Verbose bool
|
||||||
|
DebuggingVerbose bool
|
||||||
|
VerboseOut io.Writer
|
||||||
|
|
||||||
|
hostClients map[string]*http.Client
|
||||||
|
clientMu sync.Mutex
|
||||||
|
|
||||||
|
httpLogger *syncLogger
|
||||||
|
|
||||||
|
gitEnv config.Environment
|
||||||
|
osEnv config.Environment
|
||||||
|
uc *config.URLConfig
|
||||||
|
|
||||||
|
sshTries int
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewClient(ctx Context) (*Client, error) {
|
||||||
|
if ctx == nil {
|
||||||
|
ctx = NewContext(nil, nil, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
gitEnv := ctx.GitEnv()
|
||||||
|
osEnv := ctx.OSEnv()
|
||||||
|
|
||||||
|
cacheCreds := gitEnv.Bool("lfs.cachecredentials", true)
|
||||||
|
var sshResolver SSHResolver = &sshAuthClient{os: osEnv, git: gitEnv}
|
||||||
|
if cacheCreds {
|
||||||
|
sshResolver = withSSHCache(sshResolver)
|
||||||
|
}
|
||||||
|
|
||||||
|
c := &Client{
|
||||||
|
SSH: sshResolver,
|
||||||
|
DialTimeout: gitEnv.Int("lfs.dialtimeout", 0),
|
||||||
|
KeepaliveTimeout: gitEnv.Int("lfs.keepalive", 0),
|
||||||
|
TLSTimeout: gitEnv.Int("lfs.tlstimeout", 0),
|
||||||
|
ConcurrentTransfers: gitEnv.Int("lfs.concurrenttransfers", 3),
|
||||||
|
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),
|
||||||
|
gitEnv: gitEnv,
|
||||||
|
osEnv: osEnv,
|
||||||
|
uc: config.NewURLConfig(gitEnv),
|
||||||
|
sshTries: gitEnv.Int("lfs.ssh.retries", 5),
|
||||||
|
}
|
||||||
|
|
||||||
|
return c, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) GitEnv() config.Environment {
|
||||||
|
return c.gitEnv
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) OSEnv() config.Environment {
|
||||||
|
return c.osEnv
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) URLConfig() *config.URLConfig {
|
||||||
|
return c.uc
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) NewRequest(method string, e Endpoint, suffix string, body interface{}) (*http.Request, error) {
|
||||||
|
if strings.HasPrefix(e.Url, "file://") {
|
||||||
|
// Initial `\n` to avoid overprinting `Downloading LFS...`.
|
||||||
|
fmt.Fprintf(os.Stderr, "\n%s\n", hintFileUrl)
|
||||||
|
}
|
||||||
|
|
||||||
|
sshRes, err := c.sshResolveWithRetries(e, method)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
prefix := e.Url
|
||||||
|
if len(sshRes.Href) > 0 {
|
||||||
|
prefix = sshRes.Href
|
||||||
|
}
|
||||||
|
|
||||||
|
if !httpRE.MatchString(prefix) {
|
||||||
|
urlfragment := strings.SplitN(prefix, "?", 2)[0]
|
||||||
|
return nil, fmt.Errorf("missing protocol: %q", urlfragment)
|
||||||
|
}
|
||||||
|
|
||||||
|
req, err := http.NewRequest(method, joinURL(prefix, suffix), nil)
|
||||||
|
if err != nil {
|
||||||
|
return req, err
|
||||||
|
}
|
||||||
|
|
||||||
|
for key, value := range sshRes.Header {
|
||||||
|
req.Header.Set(key, value)
|
||||||
|
}
|
||||||
|
req.Header.Set("Accept", MediaType)
|
||||||
|
|
||||||
|
if body != nil {
|
||||||
|
if merr := MarshalToRequest(req, body); merr != nil {
|
||||||
|
return req, merr
|
||||||
|
}
|
||||||
|
req.Header.Set("Content-Type", MediaType)
|
||||||
|
}
|
||||||
|
|
||||||
|
return req, err
|
||||||
|
}
|
||||||
|
|
||||||
|
const slash = "/"
|
||||||
|
|
||||||
|
func joinURL(prefix, suffix string) string {
|
||||||
|
if strings.HasSuffix(prefix, slash) {
|
||||||
|
return prefix + suffix
|
||||||
|
}
|
||||||
|
return prefix + slash + suffix
|
||||||
|
}
|
||||||
|
|
||||||
|
// Do sends an HTTP request to get an HTTP response. It wraps net/http, adding
|
||||||
|
// extra headers, redirection handling, and error reporting.
|
||||||
|
func (c *Client) Do(req *http.Request) (*http.Response, error) {
|
||||||
|
req.Header = c.ExtraHeadersFor(req)
|
||||||
|
|
||||||
|
return c.do(req, "", nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
// do performs an *http.Request respecting redirects, and handles the response
|
||||||
|
// as defined in c.handleResponse. Notably, it does not alter the headers for
|
||||||
|
// the request argument in any way.
|
||||||
|
func (c *Client) do(req *http.Request, remote string, via []*http.Request) (*http.Response, error) {
|
||||||
|
req.Header.Set("User-Agent", UserAgent)
|
||||||
|
|
||||||
|
res, err := c.doWithRedirects(c.HttpClient(req.Host), req, remote, via)
|
||||||
|
if err != nil {
|
||||||
|
return res, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return res, c.handleResponse(res)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close closes any resources that this client opened.
|
||||||
|
func (c *Client) Close() error {
|
||||||
|
return c.httpLogger.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) sshResolveWithRetries(e Endpoint, method string) (*sshAuthResponse, error) {
|
||||||
|
var sshRes sshAuthResponse
|
||||||
|
var err error
|
||||||
|
|
||||||
|
requests := tools.MaxInt(0, c.sshTries) + 1
|
||||||
|
for i := 0; i < requests; i++ {
|
||||||
|
sshRes, err = c.SSH.Resolve(e, method)
|
||||||
|
if err == nil {
|
||||||
|
return &sshRes, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
tracerx.Printf(
|
||||||
|
"ssh: %s failed, error: %s, message: %s (try: %d/%d)",
|
||||||
|
e.SshUserAndHost, err.Error(), sshRes.Message, i,
|
||||||
|
requests,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(sshRes.Message) > 0 {
|
||||||
|
return nil, errors.Wrap(err, sshRes.Message)
|
||||||
|
}
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) ExtraHeadersFor(req *http.Request) http.Header {
|
||||||
|
extraHeaders := c.extraHeaders(req.URL)
|
||||||
|
if len(extraHeaders) == 0 {
|
||||||
|
return req.Header
|
||||||
|
}
|
||||||
|
|
||||||
|
copy := make(http.Header, len(req.Header))
|
||||||
|
for k, vs := range req.Header {
|
||||||
|
copy[k] = vs
|
||||||
|
}
|
||||||
|
|
||||||
|
for k, vs := range extraHeaders {
|
||||||
|
for _, v := range vs {
|
||||||
|
copy[k] = append(copy[k], v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return copy
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) extraHeaders(u *url.URL) map[string][]string {
|
||||||
|
hdrs := c.uc.GetAll("http", u.String(), "extraHeader")
|
||||||
|
m := make(map[string][]string, len(hdrs))
|
||||||
|
|
||||||
|
for _, hdr := range hdrs {
|
||||||
|
parts := strings.SplitN(hdr, ":", 2)
|
||||||
|
if len(parts) < 2 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
k, v := parts[0], strings.TrimSpace(parts[1])
|
||||||
|
// If header keys are given in non-canonicalized form (e.g.,
|
||||||
|
// "AUTHORIZATION" as opposed to "Authorization") they will not
|
||||||
|
// be returned in calls to net/http.Header.Get().
|
||||||
|
//
|
||||||
|
// So, we avoid this problem by first canonicalizing header keys
|
||||||
|
// for extra headers.
|
||||||
|
k = textproto.CanonicalMIMEHeaderKey(k)
|
||||||
|
|
||||||
|
m[k] = append(m[k], v)
|
||||||
|
}
|
||||||
|
return m
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) DoWithRedirect(cli *http.Client, req *http.Request, remote string, via []*http.Request) (*http.Request, *http.Response, error) {
|
||||||
|
tracedReq, err := c.traceRequest(req)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var retries int
|
||||||
|
if n, ok := Retries(req); ok {
|
||||||
|
retries = n
|
||||||
|
} else {
|
||||||
|
retries = defaultRequestRetries
|
||||||
|
}
|
||||||
|
|
||||||
|
var res *http.Response
|
||||||
|
|
||||||
|
requests := tools.MaxInt(0, retries) + 1
|
||||||
|
for i := 0; i < requests; i++ {
|
||||||
|
res, err = cli.Do(req)
|
||||||
|
if err == nil {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
if seek, ok := req.Body.(io.Seeker); ok {
|
||||||
|
seek.Seek(0, io.SeekStart)
|
||||||
|
}
|
||||||
|
|
||||||
|
c.traceResponse(req, tracedReq, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
c.traceResponse(req, tracedReq, nil)
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if res == nil {
|
||||||
|
return nil, nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
c.traceResponse(req, tracedReq, res)
|
||||||
|
|
||||||
|
if res.StatusCode != 301 &&
|
||||||
|
res.StatusCode != 302 &&
|
||||||
|
res.StatusCode != 303 &&
|
||||||
|
res.StatusCode != 307 &&
|
||||||
|
res.StatusCode != 308 {
|
||||||
|
|
||||||
|
// Above are the list of 3xx status codes that we know
|
||||||
|
// how to handle below. If the status code contained in
|
||||||
|
// the HTTP response was none of them, return the (res,
|
||||||
|
// err) tuple as-is, otherwise handle the redirect.
|
||||||
|
return nil, res, c.handleResponse(res)
|
||||||
|
}
|
||||||
|
|
||||||
|
redirectTo := res.Header.Get("Location")
|
||||||
|
locurl, err := url.Parse(redirectTo)
|
||||||
|
if err == nil && !locurl.IsAbs() {
|
||||||
|
locurl = req.URL.ResolveReference(locurl)
|
||||||
|
redirectTo = locurl.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
via = append(via, req)
|
||||||
|
if len(via) >= 3 {
|
||||||
|
return nil, res, errors.New("too many redirects")
|
||||||
|
}
|
||||||
|
|
||||||
|
redirectedReq, err := newRequestForRetry(req, redirectTo)
|
||||||
|
if err != nil {
|
||||||
|
return nil, res, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return redirectedReq, nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) doWithRedirects(cli *http.Client, req *http.Request, remote string, via []*http.Request) (*http.Response, error) {
|
||||||
|
redirectedReq, res, err := c.DoWithRedirect(cli, req, remote, via)
|
||||||
|
if err != nil || res != nil {
|
||||||
|
return res, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if redirectedReq == nil {
|
||||||
|
return nil, errors.New("failed to redirect request")
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.doWithRedirects(cli, redirectedReq, remote, via)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) HttpClient(host string) *http.Client {
|
||||||
|
c.clientMu.Lock()
|
||||||
|
defer c.clientMu.Unlock()
|
||||||
|
|
||||||
|
if c.gitEnv == nil {
|
||||||
|
c.gitEnv = make(testEnv)
|
||||||
|
}
|
||||||
|
|
||||||
|
if c.osEnv == nil {
|
||||||
|
c.osEnv = make(testEnv)
|
||||||
|
}
|
||||||
|
|
||||||
|
if c.hostClients == nil {
|
||||||
|
c.hostClients = make(map[string]*http.Client)
|
||||||
|
}
|
||||||
|
|
||||||
|
if client, ok := c.hostClients[host]; ok {
|
||||||
|
return client
|
||||||
|
}
|
||||||
|
|
||||||
|
concurrentTransfers := c.ConcurrentTransfers
|
||||||
|
if concurrentTransfers < 1 {
|
||||||
|
concurrentTransfers = 8
|
||||||
|
}
|
||||||
|
|
||||||
|
dialtime := c.DialTimeout
|
||||||
|
if dialtime < 1 {
|
||||||
|
dialtime = 30
|
||||||
|
}
|
||||||
|
|
||||||
|
keepalivetime := c.KeepaliveTimeout
|
||||||
|
if keepalivetime < 1 {
|
||||||
|
keepalivetime = 1800
|
||||||
|
}
|
||||||
|
|
||||||
|
tlstime := c.TLSTimeout
|
||||||
|
if tlstime < 1 {
|
||||||
|
tlstime = 30
|
||||||
|
}
|
||||||
|
|
||||||
|
tr := &http.Transport{
|
||||||
|
Proxy: proxyFromClient(c),
|
||||||
|
TLSHandshakeTimeout: time.Duration(tlstime) * time.Second,
|
||||||
|
MaxIdleConnsPerHost: concurrentTransfers,
|
||||||
|
}
|
||||||
|
|
||||||
|
activityTimeout := 30
|
||||||
|
if v, ok := c.uc.Get("lfs", fmt.Sprintf("https://%v", host), "activitytimeout"); ok {
|
||||||
|
if i, err := strconv.Atoi(v); err == nil {
|
||||||
|
activityTimeout = i
|
||||||
|
} else {
|
||||||
|
activityTimeout = 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
dialer := &net.Dialer{
|
||||||
|
Timeout: time.Duration(dialtime) * time.Second,
|
||||||
|
KeepAlive: time.Duration(keepalivetime) * time.Second,
|
||||||
|
DualStack: true,
|
||||||
|
}
|
||||||
|
|
||||||
|
if activityTimeout > 0 {
|
||||||
|
activityDuration := time.Duration(activityTimeout) * time.Second
|
||||||
|
tr.DialContext = func(ctx context.Context, network, addr string) (net.Conn, error) {
|
||||||
|
c, err := dialer.DialContext(ctx, network, addr)
|
||||||
|
if c == nil {
|
||||||
|
return c, err
|
||||||
|
}
|
||||||
|
if tc, ok := c.(*net.TCPConn); ok {
|
||||||
|
tc.SetKeepAlive(true)
|
||||||
|
tc.SetKeepAlivePeriod(dialer.KeepAlive)
|
||||||
|
}
|
||||||
|
return &deadlineConn{Timeout: activityDuration, Conn: c}, err
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
tr.DialContext = dialer.DialContext
|
||||||
|
}
|
||||||
|
|
||||||
|
tr.TLSClientConfig = &tls.Config{}
|
||||||
|
|
||||||
|
if isClientCertEnabledForHost(c, host) {
|
||||||
|
tracerx.Printf("http: client cert for %s", host)
|
||||||
|
tr.TLSClientConfig.Certificates = []tls.Certificate{getClientCertForHost(c, host)}
|
||||||
|
tr.TLSClientConfig.BuildNameToCertificate()
|
||||||
|
}
|
||||||
|
|
||||||
|
if isCertVerificationDisabledForHost(c, host) {
|
||||||
|
tr.TLSClientConfig.InsecureSkipVerify = true
|
||||||
|
} else {
|
||||||
|
tr.TLSClientConfig.RootCAs = getRootCAsForHost(c, host)
|
||||||
|
}
|
||||||
|
|
||||||
|
httpClient := &http.Client{
|
||||||
|
Transport: tr,
|
||||||
|
CheckRedirect: func(*http.Request, []*http.Request) error {
|
||||||
|
return http.ErrUseLastResponse
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
c.hostClients[host] = httpClient
|
||||||
|
if c.VerboseOut == nil {
|
||||||
|
c.VerboseOut = os.Stderr
|
||||||
|
}
|
||||||
|
|
||||||
|
return httpClient
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) CurrentUser() (string, string) {
|
||||||
|
userName, _ := c.gitEnv.Get("user.name")
|
||||||
|
userEmail, _ := c.gitEnv.Get("user.email")
|
||||||
|
return userName, userEmail
|
||||||
|
}
|
||||||
|
|
||||||
|
func newRequestForRetry(req *http.Request, location string) (*http.Request, error) {
|
||||||
|
newReq, err := http.NewRequest(req.Method, location, nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if req.URL.Scheme == "https" && newReq.URL.Scheme == "http" {
|
||||||
|
return nil, errors.New("lfsapi/client: refusing insecure redirect, https->http")
|
||||||
|
}
|
||||||
|
|
||||||
|
sameHost := req.URL.Host == newReq.URL.Host
|
||||||
|
for key := range req.Header {
|
||||||
|
if key == "Authorization" {
|
||||||
|
if !sameHost {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
newReq.Header.Set(key, req.Header.Get(key))
|
||||||
|
}
|
||||||
|
|
||||||
|
oldestURL := strings.SplitN(req.URL.String(), "?", 2)[0]
|
||||||
|
newURL := strings.SplitN(newReq.URL.String(), "?", 2)[0]
|
||||||
|
tracerx.Printf("api: redirect %s %s to %s", req.Method, oldestURL, newURL)
|
||||||
|
|
||||||
|
// This body will have already been rewound from a call to
|
||||||
|
// lfsapi.Client.traceRequest().
|
||||||
|
newReq.Body = req.Body
|
||||||
|
newReq.ContentLength = req.ContentLength
|
||||||
|
|
||||||
|
// Copy the request's context.Context, if any.
|
||||||
|
newReq = newReq.WithContext(req.Context())
|
||||||
|
|
||||||
|
return newReq, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type deadlineConn struct {
|
||||||
|
Timeout time.Duration
|
||||||
|
net.Conn
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *deadlineConn) Read(b []byte) (int, error) {
|
||||||
|
if err := c.Conn.SetDeadline(time.Now().Add(c.Timeout)); err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
return c.Conn.Read(b)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *deadlineConn) Write(b []byte) (int, error) {
|
||||||
|
if err := c.Conn.SetDeadline(time.Now().Add(c.Timeout)); err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.Conn.Write(b)
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
UserAgent = config.VersionDesc
|
||||||
|
}
|
||||||
|
|
||||||
|
type testEnv map[string]string
|
||||||
|
|
||||||
|
func (e testEnv) Get(key string) (v string, ok bool) {
|
||||||
|
v, ok = e[key]
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e testEnv) GetAll(key string) []string {
|
||||||
|
if v, ok := e.Get(key); ok {
|
||||||
|
return []string{v}
|
||||||
|
}
|
||||||
|
return make([]string, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e testEnv) Int(key string, def int) int {
|
||||||
|
s, _ := e.Get(key)
|
||||||
|
return config.Int(s, def)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e testEnv) Bool(key string, def bool) bool {
|
||||||
|
s, _ := e.Get(key)
|
||||||
|
return config.Bool(s, def)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e testEnv) All() map[string][]string {
|
||||||
|
m := make(map[string][]string)
|
||||||
|
for k, _ := range e {
|
||||||
|
m[k] = e.GetAll(k)
|
||||||
|
}
|
||||||
|
return m
|
||||||
|
}
|
@ -1,13 +1,11 @@
|
|||||||
package lfsapi
|
package lfshttp
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/base64"
|
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/http/httptest"
|
"net/http/httptest"
|
||||||
"strings"
|
|
||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
@ -177,80 +175,6 @@ func TestClientRedirect(t *testing.T) {
|
|||||||
assert.EqualError(t, err, "lfsapi/client: refusing insecure redirect, https->http")
|
assert.EqualError(t, err, "lfsapi/client: refusing insecure redirect, https->http")
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestClientRedirectReauthenticate(t *testing.T) {
|
|
||||||
var srv1, srv2 *httptest.Server
|
|
||||||
var called1, called2 uint32
|
|
||||||
var creds1, creds2 Creds
|
|
||||||
|
|
||||||
srv1 = httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
atomic.AddUint32(&called1, 1)
|
|
||||||
|
|
||||||
if hdr := r.Header.Get("Authorization"); len(hdr) > 0 {
|
|
||||||
parts := strings.SplitN(hdr, " ", 2)
|
|
||||||
typ, b64 := parts[0], parts[1]
|
|
||||||
|
|
||||||
auth, err := base64.URLEncoding.DecodeString(b64)
|
|
||||||
assert.Nil(t, err)
|
|
||||||
assert.Equal(t, "Basic", typ)
|
|
||||||
assert.Equal(t, "user1:pass1", string(auth))
|
|
||||||
|
|
||||||
http.Redirect(w, r, srv2.URL+r.URL.Path, http.StatusMovedPermanently)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
w.WriteHeader(http.StatusUnauthorized)
|
|
||||||
}))
|
|
||||||
|
|
||||||
srv2 = httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
atomic.AddUint32(&called2, 1)
|
|
||||||
|
|
||||||
parts := strings.SplitN(r.Header.Get("Authorization"), " ", 2)
|
|
||||||
typ, b64 := parts[0], parts[1]
|
|
||||||
|
|
||||||
auth, err := base64.URLEncoding.DecodeString(b64)
|
|
||||||
assert.Nil(t, err)
|
|
||||||
assert.Equal(t, "Basic", typ)
|
|
||||||
assert.Equal(t, "user2:pass2", string(auth))
|
|
||||||
}))
|
|
||||||
|
|
||||||
// Change the URL of srv2 to make it appears as if it is a different
|
|
||||||
// host.
|
|
||||||
srv2.URL = strings.Replace(srv2.URL, "127.0.0.1", "0.0.0.0", 1)
|
|
||||||
|
|
||||||
creds1 = Creds(map[string]string{
|
|
||||||
"protocol": "http",
|
|
||||||
"host": strings.TrimPrefix(srv1.URL, "http://"),
|
|
||||||
|
|
||||||
"username": "user1",
|
|
||||||
"password": "pass1",
|
|
||||||
})
|
|
||||||
creds2 = Creds(map[string]string{
|
|
||||||
"protocol": "http",
|
|
||||||
"host": strings.TrimPrefix(srv2.URL, "http://"),
|
|
||||||
|
|
||||||
"username": "user2",
|
|
||||||
"password": "pass2",
|
|
||||||
})
|
|
||||||
|
|
||||||
defer srv1.Close()
|
|
||||||
defer srv2.Close()
|
|
||||||
|
|
||||||
c, err := NewClient(NewContext(nil, nil, nil))
|
|
||||||
creds := newCredentialCacher()
|
|
||||||
creds.Approve(creds1)
|
|
||||||
creds.Approve(creds2)
|
|
||||||
c.Credentials = creds
|
|
||||||
|
|
||||||
req, err := http.NewRequest("GET", srv1.URL, nil)
|
|
||||||
require.Nil(t, err)
|
|
||||||
|
|
||||||
_, err = c.DoWithAuth("", req)
|
|
||||||
assert.Nil(t, err)
|
|
||||||
|
|
||||||
// called1 is 2 since LFS tries an unauthenticated request first
|
|
||||||
assert.EqualValues(t, 2, called1)
|
|
||||||
assert.EqualValues(t, 1, called2)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestNewClient(t *testing.T) {
|
func TestNewClient(t *testing.T) {
|
||||||
c, err := NewClient(NewContext(nil, nil, map[string]string{
|
c, err := NewClient(NewContext(nil, nil, map[string]string{
|
||||||
"lfs.dialtimeout": "151",
|
"lfs.dialtimeout": "151",
|
||||||
@ -323,12 +247,10 @@ func TestNewRequest(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for _, test := range tests {
|
for _, test := range tests {
|
||||||
c, err := NewClient(NewContext(nil, nil, map[string]string{
|
c, err := NewClient(NewContext(nil, nil, nil))
|
||||||
"lfs.url": test[0],
|
|
||||||
}))
|
|
||||||
require.Nil(t, err)
|
require.Nil(t, err)
|
||||||
|
|
||||||
req, err := c.NewRequest("POST", c.Endpoints.Endpoint("", ""), test[1], nil)
|
req, err := c.NewRequest("POST", Endpoint{Url: test[0]}, test[1], nil)
|
||||||
require.Nil(t, err)
|
require.Nil(t, err)
|
||||||
assert.Equal(t, "POST", req.Method)
|
assert.Equal(t, "POST", req.Method)
|
||||||
assert.Equal(t, test[2], req.URL.String(), fmt.Sprintf("endpoint: %s, suffix: %s, expected: %s", test[0], test[1], test[2]))
|
assert.Equal(t, test[2], req.URL.String(), fmt.Sprintf("endpoint: %s, suffix: %s, expected: %s", test[0], test[1], test[2]))
|
||||||
@ -336,15 +258,13 @@ func TestNewRequest(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestNewRequestWithBody(t *testing.T) {
|
func TestNewRequestWithBody(t *testing.T) {
|
||||||
c, err := NewClient(NewContext(nil, nil, map[string]string{
|
c, err := NewClient(NewContext(nil, nil, nil))
|
||||||
"lfs.url": "https://example.com",
|
|
||||||
}))
|
|
||||||
require.Nil(t, err)
|
require.Nil(t, err)
|
||||||
|
|
||||||
body := struct {
|
body := struct {
|
||||||
Test string
|
Test string
|
||||||
}{Test: "test"}
|
}{Test: "test"}
|
||||||
req, err := c.NewRequest("POST", c.Endpoints.Endpoint("", ""), "body", body)
|
req, err := c.NewRequest("POST", Endpoint{Url: "https://example.com"}, "body", body)
|
||||||
require.Nil(t, err)
|
require.Nil(t, err)
|
||||||
|
|
||||||
assert.NotNil(t, req.Body)
|
assert.NotNil(t, req.Body)
|
@ -1,4 +1,4 @@
|
|||||||
package lfsapi
|
package lfshttp
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
@ -31,39 +31,8 @@ func endpointOperation(e Endpoint, method string) string {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// endpointFromBareSshUrl constructs a new endpoint from a bare SSH URL:
|
// EndpointFromSshUrl constructs a new endpoint from an ssh:// URL
|
||||||
//
|
func EndpointFromSshUrl(u *url.URL) Endpoint {
|
||||||
// user@host.com:path/to/repo.git or
|
|
||||||
// [user@host.com:port]:path/to/repo.git
|
|
||||||
//
|
|
||||||
func endpointFromBareSshUrl(rawurl string) Endpoint {
|
|
||||||
parts := strings.Split(rawurl, ":")
|
|
||||||
partsLen := len(parts)
|
|
||||||
if partsLen < 2 {
|
|
||||||
return Endpoint{Url: rawurl}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Treat presence of ':' as a bare URL
|
|
||||||
var newPath string
|
|
||||||
if len(parts) > 2 { // port included; really should only ever be 3 parts
|
|
||||||
// Correctly handle [host:port]:path URLs
|
|
||||||
parts[0] = strings.TrimPrefix(parts[0], "[")
|
|
||||||
parts[1] = strings.TrimSuffix(parts[1], "]")
|
|
||||||
newPath = fmt.Sprintf("%v:%v", parts[0], strings.Join(parts[1:], "/"))
|
|
||||||
} else {
|
|
||||||
newPath = strings.Join(parts, "/")
|
|
||||||
}
|
|
||||||
newrawurl := fmt.Sprintf("ssh://%v", newPath)
|
|
||||||
newu, err := url.Parse(newrawurl)
|
|
||||||
if err != nil {
|
|
||||||
return Endpoint{Url: UrlUnknown}
|
|
||||||
}
|
|
||||||
|
|
||||||
return endpointFromSshUrl(newu)
|
|
||||||
}
|
|
||||||
|
|
||||||
// endpointFromSshUrl constructs a new endpoint from an ssh:// URL
|
|
||||||
func endpointFromSshUrl(u *url.URL) Endpoint {
|
|
||||||
var endpoint Endpoint
|
var endpoint Endpoint
|
||||||
// Pull out port now, we need it separately for SSH
|
// Pull out port now, we need it separately for SSH
|
||||||
regex := regexp.MustCompile(`^([^\:]+)(?:\:(\d+))?$`)
|
regex := regexp.MustCompile(`^([^\:]+)(?:\:(\d+))?$`)
|
||||||
@ -100,18 +69,44 @@ func endpointFromSshUrl(u *url.URL) Endpoint {
|
|||||||
return endpoint
|
return endpoint
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// EndpointFromBareSshUrl constructs a new endpoint from a bare SSH URL:
|
||||||
|
//
|
||||||
|
// user@host.com:path/to/repo.git or
|
||||||
|
// [user@host.com:port]:path/to/repo.git
|
||||||
|
//
|
||||||
|
func EndpointFromBareSshUrl(rawurl string) Endpoint {
|
||||||
|
parts := strings.Split(rawurl, ":")
|
||||||
|
partsLen := len(parts)
|
||||||
|
if partsLen < 2 {
|
||||||
|
return Endpoint{Url: rawurl}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Treat presence of ':' as a bare URL
|
||||||
|
var newPath string
|
||||||
|
if len(parts) > 2 { // port included; really should only ever be 3 parts
|
||||||
|
// Correctly handle [host:port]:path URLs
|
||||||
|
parts[0] = strings.TrimPrefix(parts[0], "[")
|
||||||
|
parts[1] = strings.TrimSuffix(parts[1], "]")
|
||||||
|
newPath = fmt.Sprintf("%v:%v", parts[0], strings.Join(parts[1:], "/"))
|
||||||
|
} else {
|
||||||
|
newPath = strings.Join(parts, "/")
|
||||||
|
}
|
||||||
|
newrawurl := fmt.Sprintf("ssh://%v", newPath)
|
||||||
|
newu, err := url.Parse(newrawurl)
|
||||||
|
if err != nil {
|
||||||
|
return Endpoint{Url: UrlUnknown}
|
||||||
|
}
|
||||||
|
|
||||||
|
return EndpointFromSshUrl(newu)
|
||||||
|
}
|
||||||
|
|
||||||
// Construct a new endpoint from a HTTP URL
|
// Construct a new endpoint from a HTTP URL
|
||||||
func endpointFromHttpUrl(u *url.URL) Endpoint {
|
func EndpointFromHttpUrl(u *url.URL) Endpoint {
|
||||||
// just pass this straight through
|
// just pass this straight through
|
||||||
return Endpoint{Url: u.String()}
|
return Endpoint{Url: u.String()}
|
||||||
}
|
}
|
||||||
|
|
||||||
func endpointFromGitUrl(u *url.URL, e *endpointGitFinder) Endpoint {
|
func EndpointFromLocalPath(path string) Endpoint {
|
||||||
u.Scheme = e.gitProtocol
|
|
||||||
return Endpoint{Url: u.String()}
|
|
||||||
}
|
|
||||||
|
|
||||||
func endpointFromLocalPath(path string) Endpoint {
|
|
||||||
if !strings.HasSuffix(path, ".git") {
|
if !strings.HasSuffix(path, ".git") {
|
||||||
path = fmt.Sprintf("%s/.git", path)
|
path = fmt.Sprintf("%s/.git", path)
|
||||||
}
|
}
|
@ -1,4 +1,4 @@
|
|||||||
package lfsapi
|
package lfshttp
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
91
lfshttp/lfshttp.go
Normal file
91
lfshttp/lfshttp.go
Normal file
@ -0,0 +1,91 @@
|
|||||||
|
package lfshttp
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
"regexp"
|
||||||
|
|
||||||
|
"github.com/git-lfs/git-lfs/config"
|
||||||
|
"github.com/git-lfs/git-lfs/errors"
|
||||||
|
"github.com/git-lfs/git-lfs/git"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
lfsMediaTypeRE = regexp.MustCompile(`\Aapplication/vnd\.git\-lfs\+json(;|\z)`)
|
||||||
|
jsonMediaTypeRE = regexp.MustCompile(`\Aapplication/json(;|\z)`)
|
||||||
|
)
|
||||||
|
|
||||||
|
type Context interface {
|
||||||
|
GitConfig() *git.Configuration
|
||||||
|
OSEnv() config.Environment
|
||||||
|
GitEnv() config.Environment
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewContext(gitConf *git.Configuration, osEnv, gitEnv map[string]string) Context {
|
||||||
|
c := &testContext{gitConfig: gitConf}
|
||||||
|
if c.gitConfig == nil {
|
||||||
|
c.gitConfig = git.NewConfig("", "")
|
||||||
|
}
|
||||||
|
if osEnv != nil {
|
||||||
|
c.osEnv = testEnv(osEnv)
|
||||||
|
} else {
|
||||||
|
c.osEnv = make(testEnv)
|
||||||
|
}
|
||||||
|
|
||||||
|
if gitEnv != nil {
|
||||||
|
c.gitEnv = testEnv(gitEnv)
|
||||||
|
} else {
|
||||||
|
c.gitEnv = make(testEnv)
|
||||||
|
}
|
||||||
|
return c
|
||||||
|
}
|
||||||
|
|
||||||
|
type testContext struct {
|
||||||
|
gitConfig *git.Configuration
|
||||||
|
osEnv config.Environment
|
||||||
|
gitEnv config.Environment
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *testContext) GitConfig() *git.Configuration {
|
||||||
|
return c.gitConfig
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *testContext) OSEnv() config.Environment {
|
||||||
|
return c.osEnv
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *testContext) GitEnv() config.Environment {
|
||||||
|
return c.gitEnv
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
@ -1,13 +1,12 @@
|
|||||||
package lfsapi
|
package lfshttp
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/git-lfs/git-lfs/config"
|
"github.com/git-lfs/git-lfs/config"
|
||||||
|
|
||||||
"fmt"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// Logic is copied, with small changes, from "net/http".ProxyFromEnvironment in the go std lib.
|
// Logic is copied, with small changes, from "net/http".ProxyFromEnvironment in the go std lib.
|
@ -1,4 +1,4 @@
|
|||||||
package lfsapi
|
package lfshttp
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"net/http"
|
"net/http"
|
@ -1,4 +1,4 @@
|
|||||||
package lfsapi
|
package lfshttp
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
@ -1,4 +1,4 @@
|
|||||||
package lfsapi
|
package lfshttp
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
@ -1,4 +1,4 @@
|
|||||||
package lfsapi
|
package lfshttp
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
@ -1,12 +1,12 @@
|
|||||||
package lfsapi
|
package lfshttp
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
|
||||||
"net/url"
|
"net/url"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/git-lfs/git-lfs/errors"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
@ -216,7 +216,7 @@ func TestSSHGetLFSExeAndArgs(t *testing.T) {
|
|||||||
cli, err := NewClient(nil)
|
cli, err := NewClient(nil)
|
||||||
require.Nil(t, err)
|
require.Nil(t, err)
|
||||||
|
|
||||||
endpoint := cli.Endpoints.Endpoint("download", "")
|
endpoint := Endpoint{Operation: "download"}
|
||||||
endpoint.SshUserAndHost = "user@foo.com"
|
endpoint.SshUserAndHost = "user@foo.com"
|
||||||
endpoint.SshPath = "user/repo"
|
endpoint.SshPath = "user/repo"
|
||||||
|
|
||||||
@ -262,7 +262,7 @@ func TestSSHGetExeAndArgsSsh(t *testing.T) {
|
|||||||
}, nil))
|
}, nil))
|
||||||
require.Nil(t, err)
|
require.Nil(t, err)
|
||||||
|
|
||||||
endpoint := cli.Endpoints.Endpoint("download", "")
|
endpoint := Endpoint{Operation: "download"}
|
||||||
endpoint.SshUserAndHost = "user@foo.com"
|
endpoint.SshUserAndHost = "user@foo.com"
|
||||||
|
|
||||||
exe, args := sshFormatArgs(sshGetExeAndArgs(cli.OSEnv(), cli.GitEnv(), endpoint))
|
exe, args := sshFormatArgs(sshGetExeAndArgs(cli.OSEnv(), cli.GitEnv(), endpoint))
|
||||||
@ -277,7 +277,7 @@ func TestSSHGetExeAndArgsSshCustomPort(t *testing.T) {
|
|||||||
}, nil))
|
}, nil))
|
||||||
require.Nil(t, err)
|
require.Nil(t, err)
|
||||||
|
|
||||||
endpoint := cli.Endpoints.Endpoint("download", "")
|
endpoint := Endpoint{Operation: "download"}
|
||||||
endpoint.SshUserAndHost = "user@foo.com"
|
endpoint.SshUserAndHost = "user@foo.com"
|
||||||
endpoint.SshPort = "8888"
|
endpoint.SshPort = "8888"
|
||||||
|
|
||||||
@ -295,7 +295,7 @@ func TestSSHGetExeAndArgsPlink(t *testing.T) {
|
|||||||
}, nil))
|
}, nil))
|
||||||
require.Nil(t, err)
|
require.Nil(t, err)
|
||||||
|
|
||||||
endpoint := cli.Endpoints.Endpoint("download", "")
|
endpoint := Endpoint{Operation: "download"}
|
||||||
endpoint.SshUserAndHost = "user@foo.com"
|
endpoint.SshUserAndHost = "user@foo.com"
|
||||||
|
|
||||||
exe, args := sshFormatArgs(sshGetExeAndArgs(cli.OSEnv(), cli.GitEnv(), endpoint))
|
exe, args := sshFormatArgs(sshGetExeAndArgs(cli.OSEnv(), cli.GitEnv(), endpoint))
|
||||||
@ -312,7 +312,7 @@ func TestSSHGetExeAndArgsPlinkCustomPort(t *testing.T) {
|
|||||||
}, nil))
|
}, nil))
|
||||||
require.Nil(t, err)
|
require.Nil(t, err)
|
||||||
|
|
||||||
endpoint := cli.Endpoints.Endpoint("download", "")
|
endpoint := Endpoint{Operation: "download"}
|
||||||
endpoint.SshUserAndHost = "user@foo.com"
|
endpoint.SshUserAndHost = "user@foo.com"
|
||||||
endpoint.SshPort = "8888"
|
endpoint.SshPort = "8888"
|
||||||
|
|
||||||
@ -330,7 +330,7 @@ func TestSSHGetExeAndArgsTortoisePlink(t *testing.T) {
|
|||||||
}, nil))
|
}, nil))
|
||||||
require.Nil(t, err)
|
require.Nil(t, err)
|
||||||
|
|
||||||
endpoint := cli.Endpoints.Endpoint("download", "")
|
endpoint := Endpoint{Operation: "download"}
|
||||||
endpoint.SshUserAndHost = "user@foo.com"
|
endpoint.SshUserAndHost = "user@foo.com"
|
||||||
|
|
||||||
exe, args := sshFormatArgs(sshGetExeAndArgs(cli.OSEnv(), cli.GitEnv(), endpoint))
|
exe, args := sshFormatArgs(sshGetExeAndArgs(cli.OSEnv(), cli.GitEnv(), endpoint))
|
||||||
@ -347,7 +347,7 @@ func TestSSHGetExeAndArgsTortoisePlinkCustomPort(t *testing.T) {
|
|||||||
}, nil))
|
}, nil))
|
||||||
require.Nil(t, err)
|
require.Nil(t, err)
|
||||||
|
|
||||||
endpoint := cli.Endpoints.Endpoint("download", "")
|
endpoint := Endpoint{Operation: "download"}
|
||||||
endpoint.SshUserAndHost = "user@foo.com"
|
endpoint.SshUserAndHost = "user@foo.com"
|
||||||
endpoint.SshPort = "8888"
|
endpoint.SshPort = "8888"
|
||||||
|
|
||||||
@ -363,7 +363,7 @@ func TestSSHGetExeAndArgsSshCommandPrecedence(t *testing.T) {
|
|||||||
}, nil))
|
}, nil))
|
||||||
require.Nil(t, err)
|
require.Nil(t, err)
|
||||||
|
|
||||||
endpoint := cli.Endpoints.Endpoint("download", "")
|
endpoint := Endpoint{Operation: "download"}
|
||||||
endpoint.SshUserAndHost = "user@foo.com"
|
endpoint.SshUserAndHost = "user@foo.com"
|
||||||
|
|
||||||
exe, args := sshFormatArgs(sshGetExeAndArgs(cli.OSEnv(), cli.GitEnv(), endpoint))
|
exe, args := sshFormatArgs(sshGetExeAndArgs(cli.OSEnv(), cli.GitEnv(), endpoint))
|
||||||
@ -377,7 +377,7 @@ func TestSSHGetExeAndArgsSshCommandArgs(t *testing.T) {
|
|||||||
}, nil))
|
}, nil))
|
||||||
require.Nil(t, err)
|
require.Nil(t, err)
|
||||||
|
|
||||||
endpoint := cli.Endpoints.Endpoint("download", "")
|
endpoint := Endpoint{Operation: "download"}
|
||||||
endpoint.SshUserAndHost = "user@foo.com"
|
endpoint.SshUserAndHost = "user@foo.com"
|
||||||
|
|
||||||
exe, args := sshFormatArgs(sshGetExeAndArgs(cli.OSEnv(), cli.GitEnv(), endpoint))
|
exe, args := sshFormatArgs(sshGetExeAndArgs(cli.OSEnv(), cli.GitEnv(), endpoint))
|
||||||
@ -391,7 +391,7 @@ func TestSSHGetExeAndArgsSshCommandArgsWithMixedQuotes(t *testing.T) {
|
|||||||
}, nil))
|
}, nil))
|
||||||
require.Nil(t, err)
|
require.Nil(t, err)
|
||||||
|
|
||||||
endpoint := cli.Endpoints.Endpoint("download", "")
|
endpoint := Endpoint{Operation: "download"}
|
||||||
endpoint.SshUserAndHost = "user@foo.com"
|
endpoint.SshUserAndHost = "user@foo.com"
|
||||||
|
|
||||||
exe, args := sshFormatArgs(sshGetExeAndArgs(cli.OSEnv(), cli.GitEnv(), endpoint))
|
exe, args := sshFormatArgs(sshGetExeAndArgs(cli.OSEnv(), cli.GitEnv(), endpoint))
|
||||||
@ -405,7 +405,7 @@ func TestSSHGetExeAndArgsSshCommandCustomPort(t *testing.T) {
|
|||||||
}, nil))
|
}, nil))
|
||||||
require.Nil(t, err)
|
require.Nil(t, err)
|
||||||
|
|
||||||
endpoint := cli.Endpoints.Endpoint("download", "")
|
endpoint := Endpoint{Operation: "download"}
|
||||||
endpoint.SshUserAndHost = "user@foo.com"
|
endpoint.SshUserAndHost = "user@foo.com"
|
||||||
endpoint.SshPort = "8888"
|
endpoint.SshPort = "8888"
|
||||||
|
|
||||||
@ -422,7 +422,7 @@ func TestSSHGetExeAndArgsCoreSshCommand(t *testing.T) {
|
|||||||
}))
|
}))
|
||||||
require.Nil(t, err)
|
require.Nil(t, err)
|
||||||
|
|
||||||
endpoint := cli.Endpoints.Endpoint("download", "")
|
endpoint := Endpoint{Operation: "download"}
|
||||||
endpoint.SshUserAndHost = "user@foo.com"
|
endpoint.SshUserAndHost = "user@foo.com"
|
||||||
|
|
||||||
exe, args := sshFormatArgs(sshGetExeAndArgs(cli.OSEnv(), cli.GitEnv(), endpoint))
|
exe, args := sshFormatArgs(sshGetExeAndArgs(cli.OSEnv(), cli.GitEnv(), endpoint))
|
||||||
@ -436,7 +436,7 @@ func TestSSHGetExeAndArgsCoreSshCommandArgsWithMixedQuotes(t *testing.T) {
|
|||||||
}))
|
}))
|
||||||
require.Nil(t, err)
|
require.Nil(t, err)
|
||||||
|
|
||||||
endpoint := cli.Endpoints.Endpoint("download", "")
|
endpoint := Endpoint{Operation: "download"}
|
||||||
endpoint.SshUserAndHost = "user@foo.com"
|
endpoint.SshUserAndHost = "user@foo.com"
|
||||||
|
|
||||||
exe, args := sshFormatArgs(sshGetExeAndArgs(cli.OSEnv(), cli.GitEnv(), endpoint))
|
exe, args := sshFormatArgs(sshGetExeAndArgs(cli.OSEnv(), cli.GitEnv(), endpoint))
|
||||||
@ -450,7 +450,7 @@ func TestSSHGetExeAndArgsConfigVersusEnv(t *testing.T) {
|
|||||||
}))
|
}))
|
||||||
require.Nil(t, err)
|
require.Nil(t, err)
|
||||||
|
|
||||||
endpoint := cli.Endpoints.Endpoint("download", "")
|
endpoint := Endpoint{Operation: "download"}
|
||||||
endpoint.SshUserAndHost = "user@foo.com"
|
endpoint.SshUserAndHost = "user@foo.com"
|
||||||
|
|
||||||
exe, args := sshFormatArgs(sshGetExeAndArgs(cli.OSEnv(), cli.GitEnv(), endpoint))
|
exe, args := sshFormatArgs(sshGetExeAndArgs(cli.OSEnv(), cli.GitEnv(), endpoint))
|
||||||
@ -467,7 +467,7 @@ func TestSSHGetLFSExeAndArgsWithCustomSSH(t *testing.T) {
|
|||||||
u, err := url.Parse("ssh://git@host.com:12345/repo")
|
u, err := url.Parse("ssh://git@host.com:12345/repo")
|
||||||
require.Nil(t, err)
|
require.Nil(t, err)
|
||||||
|
|
||||||
e := endpointFromSshUrl(u)
|
e := EndpointFromSshUrl(u)
|
||||||
t.Logf("ENDPOINT: %+v", e)
|
t.Logf("ENDPOINT: %+v", e)
|
||||||
assert.Equal(t, "12345", e.SshPort)
|
assert.Equal(t, "12345", e.SshPort)
|
||||||
assert.Equal(t, "git@host.com", e.SshUserAndHost)
|
assert.Equal(t, "git@host.com", e.SshUserAndHost)
|
||||||
@ -486,7 +486,7 @@ func TestSSHGetLFSExeAndArgsInvalidOptionsAsHost(t *testing.T) {
|
|||||||
require.Nil(t, err)
|
require.Nil(t, err)
|
||||||
assert.Equal(t, "-oProxyCommand=gnome-calculator", u.Host)
|
assert.Equal(t, "-oProxyCommand=gnome-calculator", u.Host)
|
||||||
|
|
||||||
e := endpointFromSshUrl(u)
|
e := EndpointFromSshUrl(u)
|
||||||
t.Logf("ENDPOINT: %+v", e)
|
t.Logf("ENDPOINT: %+v", e)
|
||||||
assert.Equal(t, "-oProxyCommand=gnome-calculator", e.SshUserAndHost)
|
assert.Equal(t, "-oProxyCommand=gnome-calculator", e.SshUserAndHost)
|
||||||
assert.Equal(t, "repo", e.SshPath)
|
assert.Equal(t, "repo", e.SshPath)
|
||||||
@ -506,7 +506,7 @@ func TestSSHGetLFSExeAndArgsInvalidOptionsAsHostWithCustomSSH(t *testing.T) {
|
|||||||
require.Nil(t, err)
|
require.Nil(t, err)
|
||||||
assert.Equal(t, "--oProxyCommand=gnome-calculator", u.Host)
|
assert.Equal(t, "--oProxyCommand=gnome-calculator", u.Host)
|
||||||
|
|
||||||
e := endpointFromSshUrl(u)
|
e := EndpointFromSshUrl(u)
|
||||||
t.Logf("ENDPOINT: %+v", e)
|
t.Logf("ENDPOINT: %+v", e)
|
||||||
assert.Equal(t, "--oProxyCommand=gnome-calculator", e.SshUserAndHost)
|
assert.Equal(t, "--oProxyCommand=gnome-calculator", e.SshUserAndHost)
|
||||||
assert.Equal(t, "repo", e.SshPath)
|
assert.Equal(t, "repo", e.SshPath)
|
||||||
@ -524,7 +524,7 @@ func TestSSHGetExeAndArgsInvalidOptionsAsHost(t *testing.T) {
|
|||||||
require.Nil(t, err)
|
require.Nil(t, err)
|
||||||
assert.Equal(t, "-oProxyCommand=gnome-calculator", u.Host)
|
assert.Equal(t, "-oProxyCommand=gnome-calculator", u.Host)
|
||||||
|
|
||||||
e := endpointFromSshUrl(u)
|
e := EndpointFromSshUrl(u)
|
||||||
t.Logf("ENDPOINT: %+v", e)
|
t.Logf("ENDPOINT: %+v", e)
|
||||||
assert.Equal(t, "-oProxyCommand=gnome-calculator", e.SshUserAndHost)
|
assert.Equal(t, "-oProxyCommand=gnome-calculator", e.SshUserAndHost)
|
||||||
assert.Equal(t, "", e.SshPath)
|
assert.Equal(t, "", e.SshPath)
|
||||||
@ -543,7 +543,7 @@ func TestSSHGetExeAndArgsInvalidOptionsAsPath(t *testing.T) {
|
|||||||
require.Nil(t, err)
|
require.Nil(t, err)
|
||||||
assert.Equal(t, "git-host.com", u.Host)
|
assert.Equal(t, "git-host.com", u.Host)
|
||||||
|
|
||||||
e := endpointFromSshUrl(u)
|
e := EndpointFromSshUrl(u)
|
||||||
t.Logf("ENDPOINT: %+v", e)
|
t.Logf("ENDPOINT: %+v", e)
|
||||||
assert.Equal(t, "git@git-host.com", e.SshUserAndHost)
|
assert.Equal(t, "git@git-host.com", e.SshUserAndHost)
|
||||||
assert.Equal(t, "-oProxyCommand=gnome-calculator", e.SshPath)
|
assert.Equal(t, "-oProxyCommand=gnome-calculator", e.SshPath)
|
||||||
@ -555,22 +555,22 @@ func TestSSHGetExeAndArgsInvalidOptionsAsPath(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestParseBareSSHUrl(t *testing.T) {
|
func TestParseBareSSHUrl(t *testing.T) {
|
||||||
e := endpointFromBareSshUrl("git@git-host.com:repo.git")
|
e := EndpointFromBareSshUrl("git@git-host.com:repo.git")
|
||||||
t.Logf("endpoint: %+v", e)
|
t.Logf("endpoint: %+v", e)
|
||||||
assert.Equal(t, "git@git-host.com", e.SshUserAndHost)
|
assert.Equal(t, "git@git-host.com", e.SshUserAndHost)
|
||||||
assert.Equal(t, "repo.git", e.SshPath)
|
assert.Equal(t, "repo.git", e.SshPath)
|
||||||
|
|
||||||
e = endpointFromBareSshUrl("git@git-host.com/should-be-a-colon.git")
|
e = EndpointFromBareSshUrl("git@git-host.com/should-be-a-colon.git")
|
||||||
t.Logf("endpoint: %+v", e)
|
t.Logf("endpoint: %+v", e)
|
||||||
assert.Equal(t, "", e.SshUserAndHost)
|
assert.Equal(t, "", e.SshUserAndHost)
|
||||||
assert.Equal(t, "", e.SshPath)
|
assert.Equal(t, "", e.SshPath)
|
||||||
|
|
||||||
e = endpointFromBareSshUrl("-oProxyCommand=gnome-calculator")
|
e = EndpointFromBareSshUrl("-oProxyCommand=gnome-calculator")
|
||||||
t.Logf("endpoint: %+v", e)
|
t.Logf("endpoint: %+v", e)
|
||||||
assert.Equal(t, "", e.SshUserAndHost)
|
assert.Equal(t, "", e.SshUserAndHost)
|
||||||
assert.Equal(t, "", e.SshPath)
|
assert.Equal(t, "", e.SshPath)
|
||||||
|
|
||||||
e = endpointFromBareSshUrl("git@git-host.com:-oProxyCommand=gnome-calculator")
|
e = EndpointFromBareSshUrl("git@git-host.com:-oProxyCommand=gnome-calculator")
|
||||||
t.Logf("endpoint: %+v", e)
|
t.Logf("endpoint: %+v", e)
|
||||||
assert.Equal(t, "git@git-host.com", e.SshUserAndHost)
|
assert.Equal(t, "git@git-host.com", e.SshUserAndHost)
|
||||||
assert.Equal(t, "-oProxyCommand=gnome-calculator", e.SshPath)
|
assert.Equal(t, "-oProxyCommand=gnome-calculator", e.SshPath)
|
||||||
@ -584,7 +584,7 @@ func TestSSHGetExeAndArgsPlinkCommand(t *testing.T) {
|
|||||||
}, nil))
|
}, nil))
|
||||||
require.Nil(t, err)
|
require.Nil(t, err)
|
||||||
|
|
||||||
endpoint := cli.Endpoints.Endpoint("download", "")
|
endpoint := Endpoint{Operation: "download"}
|
||||||
endpoint.SshUserAndHost = "user@foo.com"
|
endpoint.SshUserAndHost = "user@foo.com"
|
||||||
|
|
||||||
exe, args := sshFormatArgs(sshGetExeAndArgs(cli.OSEnv(), cli.GitEnv(), endpoint))
|
exe, args := sshFormatArgs(sshGetExeAndArgs(cli.OSEnv(), cli.GitEnv(), endpoint))
|
||||||
@ -600,7 +600,7 @@ func TestSSHGetExeAndArgsPlinkCommandCustomPort(t *testing.T) {
|
|||||||
}, nil))
|
}, nil))
|
||||||
require.Nil(t, err)
|
require.Nil(t, err)
|
||||||
|
|
||||||
endpoint := cli.Endpoints.Endpoint("download", "")
|
endpoint := Endpoint{Operation: "download"}
|
||||||
endpoint.SshUserAndHost = "user@foo.com"
|
endpoint.SshUserAndHost = "user@foo.com"
|
||||||
endpoint.SshPort = "8888"
|
endpoint.SshPort = "8888"
|
||||||
|
|
||||||
@ -617,7 +617,7 @@ func TestSSHGetExeAndArgsTortoisePlinkCommand(t *testing.T) {
|
|||||||
}, nil))
|
}, nil))
|
||||||
require.Nil(t, err)
|
require.Nil(t, err)
|
||||||
|
|
||||||
endpoint := cli.Endpoints.Endpoint("download", "")
|
endpoint := Endpoint{Operation: "download"}
|
||||||
endpoint.SshUserAndHost = "user@foo.com"
|
endpoint.SshUserAndHost = "user@foo.com"
|
||||||
|
|
||||||
exe, args := sshFormatArgs(sshGetExeAndArgs(cli.OSEnv(), cli.GitEnv(), endpoint))
|
exe, args := sshFormatArgs(sshGetExeAndArgs(cli.OSEnv(), cli.GitEnv(), endpoint))
|
||||||
@ -633,7 +633,7 @@ func TestSSHGetExeAndArgsTortoisePlinkCommandCustomPort(t *testing.T) {
|
|||||||
}, nil))
|
}, nil))
|
||||||
require.Nil(t, err)
|
require.Nil(t, err)
|
||||||
|
|
||||||
endpoint := cli.Endpoints.Endpoint("download", "")
|
endpoint := Endpoint{Operation: "download"}
|
||||||
endpoint.SshUserAndHost = "user@foo.com"
|
endpoint.SshUserAndHost = "user@foo.com"
|
||||||
endpoint.SshPort = "8888"
|
endpoint.SshPort = "8888"
|
||||||
|
|
@ -1,4 +1,4 @@
|
|||||||
package lfsapi
|
package lfshttp
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
@ -1,4 +1,4 @@
|
|||||||
package lfsapi
|
package lfshttp
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
@ -1,4 +1,4 @@
|
|||||||
package lfsapi
|
package lfshttp
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bufio"
|
"bufio"
|
@ -1,4 +1,4 @@
|
|||||||
package lfsapi
|
package lfshttp
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
@ -7,6 +7,7 @@ import (
|
|||||||
|
|
||||||
"github.com/git-lfs/git-lfs/git"
|
"github.com/git-lfs/git-lfs/git"
|
||||||
"github.com/git-lfs/git-lfs/lfsapi"
|
"github.com/git-lfs/git-lfs/lfsapi"
|
||||||
|
"github.com/git-lfs/git-lfs/lfshttp"
|
||||||
)
|
)
|
||||||
|
|
||||||
type lockClient struct {
|
type lockClient struct {
|
||||||
@ -56,14 +57,14 @@ func (c *lockClient) Lock(remote string, lockReq *lockRequest) (*lockResponse, *
|
|||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
req = c.LogRequest(req, "lfs.locks.lock")
|
req = c.Client.LogRequest(req, "lfs.locks.lock")
|
||||||
res, err := c.DoWithAuth(remote, req)
|
res, err := c.DoWithAuth(remote, req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, res, err
|
return nil, res, err
|
||||||
}
|
}
|
||||||
|
|
||||||
lockRes := &lockResponse{}
|
lockRes := &lockResponse{}
|
||||||
return lockRes, res, lfsapi.DecodeJSON(res, lockRes)
|
return lockRes, res, lfshttp.DecodeJSON(res, lockRes)
|
||||||
}
|
}
|
||||||
|
|
||||||
// UnlockRequest encapsulates the data sent in an API request to remove a lock.
|
// UnlockRequest encapsulates the data sent in an API request to remove a lock.
|
||||||
@ -101,14 +102,14 @@ func (c *lockClient) Unlock(ref *git.Ref, remote, id string, force bool) (*unloc
|
|||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
req = c.LogRequest(req, "lfs.locks.unlock")
|
req = c.Client.LogRequest(req, "lfs.locks.unlock")
|
||||||
res, err := c.DoWithAuth(remote, req)
|
res, err := c.DoWithAuth(remote, req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, res, err
|
return nil, res, err
|
||||||
}
|
}
|
||||||
|
|
||||||
unlockRes := &unlockResponse{}
|
unlockRes := &unlockResponse{}
|
||||||
err = lfsapi.DecodeJSON(res, unlockRes)
|
err = lfshttp.DecodeJSON(res, unlockRes)
|
||||||
return unlockRes, res, err
|
return unlockRes, res, err
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -191,7 +192,7 @@ func (c *lockClient) Search(remote string, searchReq *lockSearchRequest) (*lockL
|
|||||||
}
|
}
|
||||||
req.URL.RawQuery = q.Encode()
|
req.URL.RawQuery = q.Encode()
|
||||||
|
|
||||||
req = c.LogRequest(req, "lfs.locks.search")
|
req = c.Client.LogRequest(req, "lfs.locks.search")
|
||||||
res, err := c.DoWithAuth(remote, req)
|
res, err := c.DoWithAuth(remote, req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, res, err
|
return nil, res, err
|
||||||
@ -199,7 +200,7 @@ func (c *lockClient) Search(remote string, searchReq *lockSearchRequest) (*lockL
|
|||||||
|
|
||||||
locks := &lockList{}
|
locks := &lockList{}
|
||||||
if res.StatusCode == http.StatusOK {
|
if res.StatusCode == http.StatusOK {
|
||||||
err = lfsapi.DecodeJSON(res, locks)
|
err = lfshttp.DecodeJSON(res, locks)
|
||||||
}
|
}
|
||||||
|
|
||||||
return locks, res, err
|
return locks, res, err
|
||||||
@ -251,7 +252,7 @@ func (c *lockClient) SearchVerifiable(remote string, vreq *lockVerifiableRequest
|
|||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
req = c.LogRequest(req, "lfs.locks.verify")
|
req = c.Client.LogRequest(req, "lfs.locks.verify")
|
||||||
res, err := c.DoWithAuth(remote, req)
|
res, err := c.DoWithAuth(remote, req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, res, err
|
return nil, res, err
|
||||||
@ -259,7 +260,7 @@ func (c *lockClient) SearchVerifiable(remote string, vreq *lockVerifiableRequest
|
|||||||
|
|
||||||
locks := &lockVerifiableList{}
|
locks := &lockVerifiableList{}
|
||||||
if res.StatusCode == http.StatusOK {
|
if res.StatusCode == http.StatusOK {
|
||||||
err = lfsapi.DecodeJSON(res, locks)
|
err = lfshttp.DecodeJSON(res, locks)
|
||||||
}
|
}
|
||||||
|
|
||||||
return locks, res, err
|
return locks, res, err
|
||||||
|
@ -12,6 +12,7 @@ import (
|
|||||||
|
|
||||||
"github.com/git-lfs/git-lfs/git"
|
"github.com/git-lfs/git-lfs/git"
|
||||||
"github.com/git-lfs/git-lfs/lfsapi"
|
"github.com/git-lfs/git-lfs/lfsapi"
|
||||||
|
"github.com/git-lfs/git-lfs/lfshttp"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
"github.com/xeipuuv/gojsonschema"
|
"github.com/xeipuuv/gojsonschema"
|
||||||
@ -28,8 +29,8 @@ func TestAPILock(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
assert.Equal(t, "POST", r.Method)
|
assert.Equal(t, "POST", r.Method)
|
||||||
assert.Equal(t, lfsapi.MediaType, r.Header.Get("Accept"))
|
assert.Equal(t, lfshttp.MediaType, r.Header.Get("Accept"))
|
||||||
assert.Equal(t, lfsapi.MediaType, r.Header.Get("Content-Type"))
|
assert.Equal(t, lfshttp.MediaType, r.Header.Get("Content-Type"))
|
||||||
assert.Equal(t, "53", r.Header.Get("Content-Length"))
|
assert.Equal(t, "53", r.Header.Get("Content-Length"))
|
||||||
|
|
||||||
reqLoader, body := gojsonschema.NewReaderLoader(r.Body)
|
reqLoader, body := gojsonschema.NewReaderLoader(r.Body)
|
||||||
@ -54,7 +55,7 @@ func TestAPILock(t *testing.T) {
|
|||||||
}))
|
}))
|
||||||
defer srv.Close()
|
defer srv.Close()
|
||||||
|
|
||||||
c, err := lfsapi.NewClient(lfsapi.NewContext(nil, nil, map[string]string{
|
c, err := lfsapi.NewClient(lfshttp.NewContext(nil, nil, map[string]string{
|
||||||
"lfs.url": srv.URL + "/api",
|
"lfs.url": srv.URL + "/api",
|
||||||
}))
|
}))
|
||||||
require.Nil(t, err)
|
require.Nil(t, err)
|
||||||
@ -78,8 +79,8 @@ func TestAPIUnlock(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
assert.Equal(t, "POST", r.Method)
|
assert.Equal(t, "POST", r.Method)
|
||||||
assert.Equal(t, lfsapi.MediaType, r.Header.Get("Accept"))
|
assert.Equal(t, lfshttp.MediaType, r.Header.Get("Accept"))
|
||||||
assert.Equal(t, lfsapi.MediaType, r.Header.Get("Content-Type"))
|
assert.Equal(t, lfshttp.MediaType, r.Header.Get("Content-Type"))
|
||||||
|
|
||||||
reqLoader, body := gojsonschema.NewReaderLoader(r.Body)
|
reqLoader, body := gojsonschema.NewReaderLoader(r.Body)
|
||||||
unlockReq := &unlockRequest{}
|
unlockReq := &unlockRequest{}
|
||||||
@ -102,7 +103,7 @@ func TestAPIUnlock(t *testing.T) {
|
|||||||
}))
|
}))
|
||||||
defer srv.Close()
|
defer srv.Close()
|
||||||
|
|
||||||
c, err := lfsapi.NewClient(lfsapi.NewContext(nil, nil, map[string]string{
|
c, err := lfsapi.NewClient(lfshttp.NewContext(nil, nil, map[string]string{
|
||||||
"lfs.url": srv.URL + "/api",
|
"lfs.url": srv.URL + "/api",
|
||||||
}))
|
}))
|
||||||
require.Nil(t, err)
|
require.Nil(t, err)
|
||||||
@ -129,7 +130,7 @@ func TestAPISearch(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
assert.Equal(t, "GET", r.Method)
|
assert.Equal(t, "GET", r.Method)
|
||||||
assert.Equal(t, lfsapi.MediaType, r.Header.Get("Accept"))
|
assert.Equal(t, lfshttp.MediaType, r.Header.Get("Accept"))
|
||||||
assert.Equal(t, "", r.Header.Get("Content-Type"))
|
assert.Equal(t, "", r.Header.Get("Content-Type"))
|
||||||
|
|
||||||
q := r.URL.Query()
|
q := r.URL.Query()
|
||||||
@ -150,7 +151,7 @@ func TestAPISearch(t *testing.T) {
|
|||||||
}))
|
}))
|
||||||
defer srv.Close()
|
defer srv.Close()
|
||||||
|
|
||||||
c, err := lfsapi.NewClient(lfsapi.NewContext(nil, nil, map[string]string{
|
c, err := lfsapi.NewClient(lfshttp.NewContext(nil, nil, map[string]string{
|
||||||
"lfs.url": srv.URL + "/api",
|
"lfs.url": srv.URL + "/api",
|
||||||
}))
|
}))
|
||||||
require.Nil(t, err)
|
require.Nil(t, err)
|
||||||
@ -180,8 +181,8 @@ func TestAPIVerifiableLocks(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
assert.Equal(t, "POST", r.Method)
|
assert.Equal(t, "POST", r.Method)
|
||||||
assert.Equal(t, lfsapi.MediaType, r.Header.Get("Accept"))
|
assert.Equal(t, lfshttp.MediaType, r.Header.Get("Accept"))
|
||||||
assert.Equal(t, lfsapi.MediaType, r.Header.Get("Content-Type"))
|
assert.Equal(t, lfshttp.MediaType, r.Header.Get("Content-Type"))
|
||||||
|
|
||||||
body := lockVerifiableRequest{}
|
body := lockVerifiableRequest{}
|
||||||
if assert.Nil(t, json.NewDecoder(r.Body).Decode(&body)) {
|
if assert.Nil(t, json.NewDecoder(r.Body).Decode(&body)) {
|
||||||
@ -205,7 +206,7 @@ func TestAPIVerifiableLocks(t *testing.T) {
|
|||||||
}))
|
}))
|
||||||
defer srv.Close()
|
defer srv.Close()
|
||||||
|
|
||||||
c, err := lfsapi.NewClient(lfsapi.NewContext(nil, nil, map[string]string{
|
c, err := lfsapi.NewClient(lfshttp.NewContext(nil, nil, map[string]string{
|
||||||
"lfs.url": srv.URL + "/api",
|
"lfs.url": srv.URL + "/api",
|
||||||
}))
|
}))
|
||||||
require.Nil(t, err)
|
require.Nil(t, err)
|
||||||
|
@ -10,6 +10,7 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/git-lfs/git-lfs/lfsapi"
|
"github.com/git-lfs/git-lfs/lfsapi"
|
||||||
|
"github.com/git-lfs/git-lfs/lfshttp"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
@ -48,7 +49,7 @@ func TestRefreshCache(t *testing.T) {
|
|||||||
srv.Close()
|
srv.Close()
|
||||||
}()
|
}()
|
||||||
|
|
||||||
lfsclient, err := lfsapi.NewClient(lfsapi.NewContext(nil, nil, map[string]string{
|
lfsclient, err := lfsapi.NewClient(lfshttp.NewContext(nil, nil, map[string]string{
|
||||||
"lfs.url": srv.URL + "/api",
|
"lfs.url": srv.URL + "/api",
|
||||||
"user.name": "Fred",
|
"user.name": "Fred",
|
||||||
"user.email": "fred@bloggs.com",
|
"user.email": "fred@bloggs.com",
|
||||||
@ -120,7 +121,7 @@ func TestGetVerifiableLocks(t *testing.T) {
|
|||||||
|
|
||||||
defer srv.Close()
|
defer srv.Close()
|
||||||
|
|
||||||
lfsclient, err := lfsapi.NewClient(lfsapi.NewContext(nil, nil, map[string]string{
|
lfsclient, err := lfsapi.NewClient(lfshttp.NewContext(nil, nil, map[string]string{
|
||||||
"lfs.url": srv.URL + "/api",
|
"lfs.url": srv.URL + "/api",
|
||||||
"user.name": "Fred",
|
"user.name": "Fred",
|
||||||
"user.email": "fred@bloggs.com",
|
"user.email": "fred@bloggs.com",
|
||||||
|
11
tq/api.go
11
tq/api.go
@ -6,6 +6,7 @@ import (
|
|||||||
"github.com/git-lfs/git-lfs/errors"
|
"github.com/git-lfs/git-lfs/errors"
|
||||||
"github.com/git-lfs/git-lfs/git"
|
"github.com/git-lfs/git-lfs/git"
|
||||||
"github.com/git-lfs/git-lfs/lfsapi"
|
"github.com/git-lfs/git-lfs/lfsapi"
|
||||||
|
"github.com/git-lfs/git-lfs/lfshttp"
|
||||||
"github.com/rubyist/tracerx"
|
"github.com/rubyist/tracerx"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -28,7 +29,7 @@ type batchRequest struct {
|
|||||||
type BatchResponse struct {
|
type BatchResponse struct {
|
||||||
Objects []*Transfer `json:"objects"`
|
Objects []*Transfer `json:"objects"`
|
||||||
TransferAdapterName string `json:"transfer"`
|
TransferAdapterName string `json:"transfer"`
|
||||||
endpoint lfsapi.Endpoint
|
endpoint lfshttp.Endpoint
|
||||||
}
|
}
|
||||||
|
|
||||||
func Batch(m *Manifest, dir Direction, remote string, remoteRef *git.Ref, objects []*Transfer) (*BatchResponse, error) {
|
func Batch(m *Manifest, dir Direction, remote string, remoteRef *git.Ref, objects []*Transfer) (*BatchResponse, error) {
|
||||||
@ -64,19 +65,19 @@ func (c *tqClient) Batch(remote string, bReq *batchRequest) (*BatchResponse, err
|
|||||||
|
|
||||||
tracerx.Printf("api: batch %d files", len(bReq.Objects))
|
tracerx.Printf("api: batch %d files", len(bReq.Objects))
|
||||||
|
|
||||||
req = c.LogRequest(req, "lfs.batch")
|
req = c.Client.LogRequest(req, "lfs.batch")
|
||||||
res, err := c.DoWithAuth(remote, lfsapi.WithRetries(req, c.MaxRetries))
|
res, err := c.DoWithAuth(remote, lfshttp.WithRetries(req, c.MaxRetries))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
tracerx.Printf("api error: %s", err)
|
tracerx.Printf("api error: %s", err)
|
||||||
return nil, errors.Wrap(err, "batch response")
|
return nil, errors.Wrap(err, "batch response")
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := lfsapi.DecodeJSON(res, bRes); err != nil {
|
if err := lfshttp.DecodeJSON(res, bRes); err != nil {
|
||||||
return bRes, errors.Wrap(err, "batch response")
|
return bRes, errors.Wrap(err, "batch response")
|
||||||
}
|
}
|
||||||
|
|
||||||
if res.StatusCode != 200 {
|
if res.StatusCode != 200 {
|
||||||
return nil, lfsapi.NewStatusCodeError(res)
|
return nil, lfshttp.NewStatusCodeError(res)
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, obj := range bRes.Objects {
|
for _, obj := range bRes.Objects {
|
||||||
|
@ -11,6 +11,7 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/git-lfs/git-lfs/lfsapi"
|
"github.com/git-lfs/git-lfs/lfsapi"
|
||||||
|
"github.com/git-lfs/git-lfs/lfshttp"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
"github.com/xeipuuv/gojsonschema"
|
"github.com/xeipuuv/gojsonschema"
|
||||||
@ -54,7 +55,7 @@ func TestAPIBatch(t *testing.T) {
|
|||||||
}))
|
}))
|
||||||
defer srv.Close()
|
defer srv.Close()
|
||||||
|
|
||||||
c, err := lfsapi.NewClient(lfsapi.NewContext(nil, nil, map[string]string{
|
c, err := lfsapi.NewClient(lfshttp.NewContext(nil, nil, map[string]string{
|
||||||
"lfs.url": srv.URL + "/api",
|
"lfs.url": srv.URL + "/api",
|
||||||
}))
|
}))
|
||||||
require.Nil(t, err)
|
require.Nil(t, err)
|
||||||
@ -110,7 +111,7 @@ func TestAPIBatchOnlyBasic(t *testing.T) {
|
|||||||
}))
|
}))
|
||||||
defer srv.Close()
|
defer srv.Close()
|
||||||
|
|
||||||
c, err := lfsapi.NewClient(lfsapi.NewContext(nil, nil, map[string]string{
|
c, err := lfsapi.NewClient(lfshttp.NewContext(nil, nil, map[string]string{
|
||||||
"lfs.url": srv.URL + "/api",
|
"lfs.url": srv.URL + "/api",
|
||||||
}))
|
}))
|
||||||
require.Nil(t, err)
|
require.Nil(t, err)
|
||||||
|
@ -4,13 +4,14 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/git-lfs/git-lfs/lfsapi"
|
"github.com/git-lfs/git-lfs/lfsapi"
|
||||||
|
"github.com/git-lfs/git-lfs/lfshttp"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestCustomTransferBasicConfig(t *testing.T) {
|
func TestCustomTransferBasicConfig(t *testing.T) {
|
||||||
path := "/path/to/binary"
|
path := "/path/to/binary"
|
||||||
cli, err := lfsapi.NewClient(lfsapi.NewContext(nil, nil, map[string]string{
|
cli, err := lfsapi.NewClient(lfshttp.NewContext(nil, nil, map[string]string{
|
||||||
"lfs.customtransfer.testsimple.path": path,
|
"lfs.customtransfer.testsimple.path": path,
|
||||||
}))
|
}))
|
||||||
require.Nil(t, err)
|
require.Nil(t, err)
|
||||||
@ -36,7 +37,7 @@ func TestCustomTransferBasicConfig(t *testing.T) {
|
|||||||
func TestCustomTransferDownloadConfig(t *testing.T) {
|
func TestCustomTransferDownloadConfig(t *testing.T) {
|
||||||
path := "/path/to/binary"
|
path := "/path/to/binary"
|
||||||
args := "-c 1 --whatever"
|
args := "-c 1 --whatever"
|
||||||
cli, err := lfsapi.NewClient(lfsapi.NewContext(nil, nil, map[string]string{
|
cli, err := lfsapi.NewClient(lfshttp.NewContext(nil, nil, map[string]string{
|
||||||
"lfs.customtransfer.testdownload.path": path,
|
"lfs.customtransfer.testdownload.path": path,
|
||||||
"lfs.customtransfer.testdownload.args": args,
|
"lfs.customtransfer.testdownload.args": args,
|
||||||
"lfs.customtransfer.testdownload.concurrent": "false",
|
"lfs.customtransfer.testdownload.concurrent": "false",
|
||||||
@ -62,7 +63,7 @@ func TestCustomTransferDownloadConfig(t *testing.T) {
|
|||||||
func TestCustomTransferUploadConfig(t *testing.T) {
|
func TestCustomTransferUploadConfig(t *testing.T) {
|
||||||
path := "/path/to/binary"
|
path := "/path/to/binary"
|
||||||
args := "-c 1 --whatever"
|
args := "-c 1 --whatever"
|
||||||
cli, err := lfsapi.NewClient(lfsapi.NewContext(nil, nil, map[string]string{
|
cli, err := lfsapi.NewClient(lfshttp.NewContext(nil, nil, map[string]string{
|
||||||
"lfs.customtransfer.testupload.path": path,
|
"lfs.customtransfer.testupload.path": path,
|
||||||
"lfs.customtransfer.testupload.args": args,
|
"lfs.customtransfer.testupload.args": args,
|
||||||
"lfs.customtransfer.testupload.concurrent": "false",
|
"lfs.customtransfer.testupload.concurrent": "false",
|
||||||
@ -88,7 +89,7 @@ func TestCustomTransferUploadConfig(t *testing.T) {
|
|||||||
func TestCustomTransferBothConfig(t *testing.T) {
|
func TestCustomTransferBothConfig(t *testing.T) {
|
||||||
path := "/path/to/binary"
|
path := "/path/to/binary"
|
||||||
args := "-c 1 --whatever --yeah"
|
args := "-c 1 --whatever --yeah"
|
||||||
cli, err := lfsapi.NewClient(lfsapi.NewContext(nil, nil, map[string]string{
|
cli, err := lfsapi.NewClient(lfshttp.NewContext(nil, nil, map[string]string{
|
||||||
"lfs.customtransfer.testboth.path": path,
|
"lfs.customtransfer.testboth.path": path,
|
||||||
"lfs.customtransfer.testboth.args": args,
|
"lfs.customtransfer.testboth.args": args,
|
||||||
"lfs.customtransfer.testboth.concurrent": "yes",
|
"lfs.customtransfer.testboth.concurrent": "yes",
|
||||||
|
@ -4,12 +4,13 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/git-lfs/git-lfs/lfsapi"
|
"github.com/git-lfs/git-lfs/lfsapi"
|
||||||
|
"github.com/git-lfs/git-lfs/lfshttp"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestManifestIsConfigurable(t *testing.T) {
|
func TestManifestIsConfigurable(t *testing.T) {
|
||||||
cli, err := lfsapi.NewClient(lfsapi.NewContext(nil, nil, map[string]string{
|
cli, err := lfsapi.NewClient(lfshttp.NewContext(nil, nil, map[string]string{
|
||||||
"lfs.transfer.maxretries": "3",
|
"lfs.transfer.maxretries": "3",
|
||||||
}))
|
}))
|
||||||
require.Nil(t, err)
|
require.Nil(t, err)
|
||||||
@ -19,7 +20,7 @@ func TestManifestIsConfigurable(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestManifestChecksNTLM(t *testing.T) {
|
func TestManifestChecksNTLM(t *testing.T) {
|
||||||
cli, err := lfsapi.NewClient(lfsapi.NewContext(nil, nil, map[string]string{
|
cli, err := lfsapi.NewClient(lfshttp.NewContext(nil, nil, map[string]string{
|
||||||
"lfs.url": "http://foo",
|
"lfs.url": "http://foo",
|
||||||
"lfs.http://foo.access": "ntlm",
|
"lfs.http://foo.access": "ntlm",
|
||||||
"lfs.concurrenttransfers": "3",
|
"lfs.concurrenttransfers": "3",
|
||||||
@ -31,7 +32,7 @@ func TestManifestChecksNTLM(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestManifestClampsValidValues(t *testing.T) {
|
func TestManifestClampsValidValues(t *testing.T) {
|
||||||
cli, err := lfsapi.NewClient(lfsapi.NewContext(nil, nil, map[string]string{
|
cli, err := lfsapi.NewClient(lfshttp.NewContext(nil, nil, map[string]string{
|
||||||
"lfs.transfer.maxretries": "-1",
|
"lfs.transfer.maxretries": "-1",
|
||||||
}))
|
}))
|
||||||
require.Nil(t, err)
|
require.Nil(t, err)
|
||||||
@ -41,7 +42,7 @@ func TestManifestClampsValidValues(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestManifestIgnoresNonInts(t *testing.T) {
|
func TestManifestIgnoresNonInts(t *testing.T) {
|
||||||
cli, err := lfsapi.NewClient(lfsapi.NewContext(nil, nil, map[string]string{
|
cli, err := lfsapi.NewClient(lfshttp.NewContext(nil, nil, map[string]string{
|
||||||
"lfs.transfer.maxretries": "not_an_int",
|
"lfs.transfer.maxretries": "not_an_int",
|
||||||
}))
|
}))
|
||||||
require.Nil(t, err)
|
require.Nil(t, err)
|
||||||
|
@ -9,6 +9,7 @@ import (
|
|||||||
"github.com/git-lfs/git-lfs/errors"
|
"github.com/git-lfs/git-lfs/errors"
|
||||||
"github.com/git-lfs/git-lfs/git"
|
"github.com/git-lfs/git-lfs/git"
|
||||||
"github.com/git-lfs/git-lfs/lfsapi"
|
"github.com/git-lfs/git-lfs/lfsapi"
|
||||||
|
"github.com/git-lfs/git-lfs/lfshttp"
|
||||||
"github.com/git-lfs/git-lfs/tools"
|
"github.com/git-lfs/git-lfs/tools"
|
||||||
"github.com/rubyist/tracerx"
|
"github.com/rubyist/tracerx"
|
||||||
)
|
)
|
||||||
@ -537,7 +538,7 @@ func (q *TransferQueue) makeBatch() batch { return make(batch, 0, q.batchSize) }
|
|||||||
// closed.
|
// closed.
|
||||||
//
|
//
|
||||||
// addToAdapter returns immediately, and does not block.
|
// addToAdapter returns immediately, and does not block.
|
||||||
func (q *TransferQueue) addToAdapter(e lfsapi.Endpoint, pending []*Transfer) <-chan *objectTuple {
|
func (q *TransferQueue) addToAdapter(e lfshttp.Endpoint, pending []*Transfer) <-chan *objectTuple {
|
||||||
retries := make(chan *objectTuple, len(pending))
|
retries := make(chan *objectTuple, len(pending))
|
||||||
|
|
||||||
if err := q.ensureAdapterBegun(e); err != nil {
|
if err := q.ensureAdapterBegun(e); err != nil {
|
||||||
@ -729,7 +730,7 @@ func (q *TransferQueue) Skip(size int64) {
|
|||||||
q.meter.Skip(size)
|
q.meter.Skip(size)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (q *TransferQueue) ensureAdapterBegun(e lfsapi.Endpoint) error {
|
func (q *TransferQueue) ensureAdapterBegun(e lfshttp.Endpoint) error {
|
||||||
q.adapterInitMutex.Lock()
|
q.adapterInitMutex.Lock()
|
||||||
defer q.adapterInitMutex.Unlock()
|
defer q.adapterInitMutex.Unlock()
|
||||||
|
|
||||||
@ -760,7 +761,7 @@ func (q *TransferQueue) ensureAdapterBegun(e lfsapi.Endpoint) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (q *TransferQueue) toAdapterCfg(e lfsapi.Endpoint) AdapterConfig {
|
func (q *TransferQueue) toAdapterCfg(e lfshttp.Endpoint) AdapterConfig {
|
||||||
apiClient := q.manifest.APIClient()
|
apiClient := q.manifest.APIClient()
|
||||||
concurrency := q.manifest.ConcurrentTransfers()
|
concurrency := q.manifest.ConcurrentTransfers()
|
||||||
if apiClient.Endpoints.AccessFor(e.Url) == lfsapi.NTLMAccess {
|
if apiClient.Endpoints.AccessFor(e.Url) == lfsapi.NTLMAccess {
|
||||||
|
@ -4,6 +4,7 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/git-lfs/git-lfs/lfsapi"
|
"github.com/git-lfs/git-lfs/lfsapi"
|
||||||
|
"github.com/git-lfs/git-lfs/lfshttp"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
@ -118,7 +119,7 @@ func testAdapterRegAndOverride(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func testAdapterRegButBasicOnly(t *testing.T) {
|
func testAdapterRegButBasicOnly(t *testing.T) {
|
||||||
cli, err := lfsapi.NewClient(lfsapi.NewContext(nil, nil, map[string]string{
|
cli, err := lfsapi.NewClient(lfshttp.NewContext(nil, nil, map[string]string{
|
||||||
"lfs.basictransfersonly": "yes",
|
"lfs.basictransfersonly": "yes",
|
||||||
}))
|
}))
|
||||||
require.Nil(t, err)
|
require.Nil(t, err)
|
||||||
|
@ -8,6 +8,7 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/git-lfs/git-lfs/lfsapi"
|
"github.com/git-lfs/git-lfs/lfsapi"
|
||||||
|
"github.com/git-lfs/git-lfs/lfshttp"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
@ -44,7 +45,7 @@ func TestVerifySuccess(t *testing.T) {
|
|||||||
}))
|
}))
|
||||||
defer srv.Close()
|
defer srv.Close()
|
||||||
|
|
||||||
c, err := lfsapi.NewClient(lfsapi.NewContext(nil, nil, map[string]string{
|
c, err := lfsapi.NewClient(lfshttp.NewContext(nil, nil, map[string]string{
|
||||||
"lfs.transfer.maxverifies": "1",
|
"lfs.transfer.maxverifies": "1",
|
||||||
}))
|
}))
|
||||||
require.Nil(t, err)
|
require.Nil(t, err)
|
||||||
|
Loading…
Reference in New Issue
Block a user