Merge branch 'master' into tracked-remote

This commit is contained in:
risk danger olson 2017-10-30 13:25:44 -07:00 committed by GitHub
commit aecf6a0086
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 754 additions and 559 deletions

@ -65,13 +65,58 @@ func (e *environment) GetAll(key string) []string {
return e.Fetcher.GetAll(key)
}
func (e *environment) Bool(key string, def bool) (val bool) {
func (e *environment) Bool(key string, def bool) bool {
s, _ := e.Fetcher.Get(key)
if len(s) == 0 {
return Bool(s, def)
}
func (e *environment) Int(key string, def int) int {
s, _ := e.Fetcher.Get(key)
return Int(s, def)
}
func (e *environment) All() map[string][]string {
return e.Fetcher.All()
}
// Int returns the int value associated with the given value, or the value
// "def", if the value is blank.
//
// To convert from a the string value attached to a given key,
// `strconv.Atoi(val)` is called. If `Atoi` returned a non-nil error,
// then the value "def" will be returned instead.
//
// Otherwise, if the value was converted `string -> int` successfully,
// then it will be returned wholesale.
func Int(value string, def int) int {
if len(value) == 0 {
return def
}
switch strings.ToLower(s) {
i, err := strconv.Atoi(value)
if err != nil {
return def
}
return i
}
// Bool returns the boolean state associated with the given value, or the
// value "def", if the value is blank.
//
// The "boolean state associated with a given key" is defined as the
// case-insensitive string comparison with the following:
//
// 1) true if...
// "true", "1", "on", "yes", or "t"
// 2) false if...
// "false", "0", "off", "no", "f", or otherwise.
func Bool(value string, def bool) bool {
if len(value) == 0 {
return def
}
switch strings.ToLower(value) {
case "true", "1", "on", "yes", "t":
return true
case "false", "0", "off", "no", "f":
@ -80,21 +125,3 @@ func (e *environment) Bool(key string, def bool) (val bool) {
return false
}
}
func (e *environment) Int(key string, def int) (val int) {
s, _ := e.Fetcher.Get(key)
if len(s) == 0 {
return def
}
i, err := strconv.Atoi(s)
if err != nil {
return def
}
return i
}
func (e *environment) All() map[string][]string {
return e.Fetcher.All()
}

@ -50,6 +50,11 @@ func (c *URLConfig) GetAll(prefix, rawurl, key string) []string {
return c.git.GetAll(strings.Join([]string{prefix, key}, "."))
}
func (c *URLConfig) Bool(prefix, rawurl, key string, def bool) bool {
s, _ := c.Get(prefix, rawurl, key)
return Bool(s, def)
}
func (c *URLConfig) getAll(prefix, rawurl, key string) []string {
hosts, paths := c.hostsAndPaths(rawurl)

@ -172,8 +172,6 @@ func (o *FilterProcessScanner) Err() error { return o.err }
// will read the body of the request. Since the body is _not_ offset, one
// request should be read in its entirety before consuming the next request.
func (o *FilterProcessScanner) readRequest() (*Request, error) {
tracerx.Printf("Read filter-process request.")
requestList, err := o.pl.readPacketList()
if err != nil {
return nil, err

@ -19,8 +19,6 @@ import (
func runCatFileBatch(pointerCh chan *WrappedPointer, lockableCh chan string, lockableSet *lockableNameSet, revs *StringChannelWrapper, errCh chan error) error {
scanner, err := NewPointerScanner()
if err != nil {
scanner.Close()
return err
}

@ -20,23 +20,11 @@ var (
defaultEndpointFinder = NewEndpointFinder(nil)
)
// DoWithAuth sends an HTTP request to get an HTTP response. It attempts to add
// authentication from netrc or git's credential helpers if necessary,
// supporting basic and ntlm authentication.
func (c *Client) DoWithAuth(remote string, req *http.Request) (*http.Response, error) {
credHelper := c.Credentials
if credHelper == nil {
credHelper = defaultCredentialHelper
}
netrcFinder := c.Netrc
if netrcFinder == nil {
netrcFinder = defaultNetrcFinder
}
ef := c.Endpoints
if ef == nil {
ef = defaultEndpointFinder
}
apiEndpoint, access, creds, credsURL, err := getCreds(credHelper, netrcFinder, ef, remote, req)
apiEndpoint, access, credHelper, credsURL, creds, err := c.getCreds(remote, req)
if err != nil {
return nil, err
}
@ -94,32 +82,48 @@ 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"
// This URL is used for the Git Credential Helper. This way existing https
// Git remote credentials can be re-used for LFS.
func getCreds(credHelper CredentialHelper, netrcFinder NetrcFinder, ef EndpointFinder, remote string, req *http.Request) (Endpoint, Access, Creds, *url.URL, error) {
func (c *Client) getCreds(remote string, req *http.Request) (Endpoint, Access, CredentialHelper, *url.URL, Creds, error) {
ef := c.Endpoints
if ef == nil {
ef = defaultEndpointFinder
}
netrcFinder := c.Netrc
if netrcFinder == nil {
netrcFinder = defaultNetrcFinder
}
operation := getReqOperation(req)
apiEndpoint := ef.Endpoint(operation, remote)
access := ef.AccessFor(apiEndpoint.Url)
if access != NTLMAccess {
if requestHasAuth(req) || setAuthFromNetrc(netrcFinder, req) || access == NoneAccess {
return apiEndpoint, access, nil, nil, nil
return apiEndpoint, access, nullCreds, nil, nil, nil
}
credsURL, err := getCredURLForAPI(ef, operation, remote, apiEndpoint, req)
if err != nil {
return apiEndpoint, access, nil, nil, errors.Wrap(err, "creds")
return apiEndpoint, access, nullCreds, nil, nil, errors.Wrap(err, "creds")
}
if credsURL == nil {
return apiEndpoint, access, nil, nil, nil
return apiEndpoint, access, nullCreds, nil, nil, nil
}
creds, err := fillGitCreds(credHelper, ef, req, credsURL)
return apiEndpoint, access, creds, credsURL, err
credHelper, creds, err := c.getGitCreds(ef, req, credsURL)
if err == nil {
tracerx.Printf("Filled credentials for %s", credsURL)
setRequestAuth(req, creds["username"], creds["password"])
}
return apiEndpoint, access, credHelper, credsURL, creds, err
}
// NTLM ONLY
credsURL, err := url.Parse(apiEndpoint.Url)
if err != nil {
return apiEndpoint, access, nil, nil, errors.Wrap(err, "creds")
return apiEndpoint, access, nullCreds, nil, nil, errors.Wrap(err, "creds")
}
if netrcMachine := getAuthFromNetrc(netrcFinder, req); netrcMachine != nil {
@ -131,20 +135,16 @@ func getCreds(credHelper CredentialHelper, netrcFinder NetrcFinder, ef EndpointF
"source": "netrc",
}
return apiEndpoint, access, creds, credsURL, nil
return apiEndpoint, access, nullCreds, credsURL, creds, nil
}
creds, err := getGitCreds(credHelper, ef, req, credsURL)
return apiEndpoint, access, creds, credsURL, err
}
func getGitCreds(credHelper CredentialHelper, ef EndpointFinder, req *http.Request, u *url.URL) (Creds, error) {
path := strings.TrimPrefix(u.Path, "/")
input := Creds{"protocol": u.Scheme, "host": u.Host, "path": path}
if u.User != nil && u.User.Username() != "" {
input["username"] = u.User.Username()
// NTLM uses creds to create the session
credHelper, creds, err := c.getGitCreds(ef, req, credsURL)
return apiEndpoint, access, credHelper, credsURL, creds, err
}
func (c *Client) getGitCreds(ef EndpointFinder, req *http.Request, u *url.URL) (CredentialHelper, Creds, error) {
credHelper, input := c.getCredentialHelper(u)
creds, err := credHelper.Fill(input)
if creds == nil || len(creds) < 1 {
errmsg := fmt.Sprintf("Git credentials for %s not found", u)
@ -156,17 +156,7 @@ func getGitCreds(credHelper CredentialHelper, ef EndpointFinder, req *http.Reque
err = errors.New(errmsg)
}
return creds, err
}
func fillGitCreds(credHelper CredentialHelper, ef EndpointFinder, req *http.Request, u *url.URL) (Creds, error) {
creds, err := getGitCreds(credHelper, ef, req, u)
if err == nil {
tracerx.Printf("Filled credentials for %s", u)
setRequestAuth(req, creds["username"], creds["password"])
}
return creds, err
return credHelper, creds, err
}
func getAuthFromNetrc(netrcFinder NetrcFinder, req *http.Request) *netrc.Machine {

@ -11,6 +11,7 @@ import (
"testing"
"github.com/git-lfs/git-lfs/errors"
"github.com/git-lfs/git-lfs/git"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
@ -93,7 +94,6 @@ func TestDoWithAuthApprove(t *testing.T) {
assert.True(t, creds.IsApproved(Creds(map[string]string{
"username": "user",
"password": "pass",
"path": "repo/lfs",
"protocol": "http",
"host": srv.Listener.Addr().String(),
})))
@ -264,6 +264,51 @@ func TestGetCreds(t *testing.T) {
"lfs.url": "https://git-server.com/repo/lfs",
"lfs.https://git-server.com/repo/lfs.access": "basic",
},
Expected: getCredsExpected{
Access: BasicAccess,
Endpoint: "https://git-server.com/repo/lfs",
Authorization: basicAuth("git-server.com", "monkey"),
CredsURL: "https://git-server.com/repo/lfs",
Creds: map[string]string{
"protocol": "https",
"host": "git-server.com",
"username": "git-server.com",
"password": "monkey",
},
},
},
"basic access with usehttppath": getCredsTest{
Remote: "origin",
Method: "GET",
Href: "https://git-server.com/repo/lfs/locks",
Config: map[string]string{
"lfs.url": "https://git-server.com/repo/lfs",
"lfs.https://git-server.com/repo/lfs.access": "basic",
"credential.usehttppath": "true",
},
Expected: getCredsExpected{
Access: BasicAccess,
Endpoint: "https://git-server.com/repo/lfs",
Authorization: basicAuth("git-server.com", "monkey"),
CredsURL: "https://git-server.com/repo/lfs",
Creds: map[string]string{
"protocol": "https",
"host": "git-server.com",
"username": "git-server.com",
"password": "monkey",
"path": "repo/lfs",
},
},
},
"basic access with url-specific usehttppath": getCredsTest{
Remote: "origin",
Method: "GET",
Href: "https://git-server.com/repo/lfs/locks",
Config: map[string]string{
"lfs.url": "https://git-server.com/repo/lfs",
"lfs.https://git-server.com/repo/lfs.access": "basic",
"credential.https://git-server.com.usehttppath": "true",
},
Expected: getCredsExpected{
Access: BasicAccess,
Endpoint: "https://git-server.com/repo/lfs",
@ -295,7 +340,6 @@ func TestGetCreds(t *testing.T) {
"host": "git-server.com",
"username": "git-server.com",
"password": "monkey",
"path": "repo/lfs",
},
},
},
@ -369,7 +413,6 @@ func TestGetCreds(t *testing.T) {
"host": "git-server.com",
"username": "user",
"password": "monkey",
"path": "repo/lfs",
},
},
},
@ -392,7 +435,6 @@ func TestGetCreds(t *testing.T) {
"host": "git-server.com",
"username": "git-server.com",
"password": "monkey",
"path": "repo",
},
},
},
@ -443,7 +485,6 @@ func TestGetCreds(t *testing.T) {
"host": "git-server.com",
"username": "git-server.com",
"password": "monkey",
"path": "repo/lfs/locks",
},
},
},
@ -465,7 +506,6 @@ func TestGetCreds(t *testing.T) {
"host": "lfs-server.com",
"username": "lfs-server.com",
"password": "monkey",
"path": "repo/lfs/locks",
},
},
},
@ -487,7 +527,6 @@ func TestGetCreds(t *testing.T) {
"host": "git-server.com:8080",
"username": "git-server.com:8080",
"password": "monkey",
"path": "repo/lfs/locks",
},
},
},
@ -509,7 +548,6 @@ func TestGetCreds(t *testing.T) {
Creds: map[string]string{
"host": "git-server.com",
"password": "monkey",
"path": "repo/lfs",
"protocol": "https",
"username": "git-server.com",
},
@ -517,8 +555,6 @@ func TestGetCreds(t *testing.T) {
},
}
credHelper := &fakeCredentialFiller{}
netrcFinder := &fakeNetrc{}
for desc, test := range tests {
t.Log(desc)
req, err := http.NewRequest(test.Method, test.Href, nil)
@ -531,8 +567,12 @@ func TestGetCreds(t *testing.T) {
req.Header.Set(key, value)
}
ef := NewEndpointFinder(NewContext(nil, nil, test.Config))
endpoint, access, creds, credsURL, err := getCreds(credHelper, netrcFinder, ef, test.Remote, req)
ctx := NewContext(git.NewConfig("", ""), nil, test.Config)
client, _ := NewClient(ctx)
client.Credentials = &fakeCredentialFiller{}
client.Netrc = &fakeNetrc{}
client.Endpoints = NewEndpointFinder(ctx)
endpoint, access, _, credsURL, creds, err := client.getCreds(test.Remote, req)
if !assert.Nil(t, err) {
continue
}

@ -79,6 +79,8 @@ func joinURL(prefix, suffix string) string {
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)
req.Header.Set("User-Agent", UserAgent)

@ -8,28 +8,34 @@ import (
"strings"
"sync"
"github.com/git-lfs/git-lfs/config"
"github.com/git-lfs/git-lfs/errors"
"github.com/rubyist/tracerx"
)
// credsConfig supplies configuration options pertaining to the authorization
// process in package lfsapi.
type credsConfig struct {
// AskPass is a string containing an executable name as well as a
// program arguments.
//
// See: https://git-scm.com/docs/gitcredentials#_requesting_credentials
// for more.
AskPass string `os:"GIT_ASKPASS" git:"core.askpass" os:"SSH_ASKPASS"`
// Helper is a string defining the credential helper that Git should use.
Helper string `git:"credential.helper"`
// Cached is a boolean determining whether or not to enable the
// credential cacher.
Cached bool
// SkipPrompt is a boolean determining whether or not to prompt the user
// for a password.
SkipPrompt bool `os:"GIT_TERMINAL_PROMPT"`
// CredentialHelper is an interface used by the lfsapi Client to interact with
// the 'git credential' command: https://git-scm.com/docs/gitcredentials
// Other implementations include ASKPASS support, and an in-memory cache.
type CredentialHelper interface {
Fill(Creds) (Creds, error)
Reject(Creds) error
Approve(Creds) error
}
// Creds represents a set of key/value pairs that are passed to 'git credential'
// as input.
type Creds map[string]string
func bufferCreds(c Creds) *bytes.Buffer {
buf := new(bytes.Buffer)
for k, v := range c {
buf.Write([]byte(k))
buf.Write([]byte("="))
buf.Write([]byte(v))
buf.Write([]byte("\n"))
}
return buf
}
// getCredentialHelper parses a 'credsConfig' from the git and OS environments,
@ -37,110 +43,32 @@ type credsConfig struct {
//
// It returns an error if any configuration was invalid, or otherwise
// un-useable.
func getCredentialHelper(osEnv, gitEnv config.Environment) (CredentialHelper, error) {
ccfg, err := getCredentialConfig(osEnv, gitEnv)
if err != nil {
return nil, err
func (c *Client) getCredentialHelper(u *url.URL) (CredentialHelper, Creds) {
rawurl := fmt.Sprintf("%s://%s%s", u.Scheme, u.Host, u.Path)
input := Creds{"protocol": u.Scheme, "host": u.Host}
if u.User != nil && u.User.Username() != "" {
input["username"] = u.User.Username()
}
if c.uc.Bool("credential", rawurl, "usehttppath", false) {
input["path"] = strings.TrimPrefix(u.Path, "/")
}
var hs []CredentialHelper
if len(ccfg.Helper) == 0 && len(ccfg.AskPass) > 0 {
hs = append(hs, &AskPassCredentialHelper{
Program: ccfg.AskPass,
})
if c.Credentials != nil {
return c.Credentials, input
}
var h CredentialHelper
h = &commandCredentialHelper{
SkipPrompt: ccfg.SkipPrompt,
helpers := make([]CredentialHelper, 0, 3)
if c.cachingCredHelper != nil {
helpers = append(helpers, c.cachingCredHelper)
}
if ccfg.Cached {
h = withCredentialCache(h)
}
hs = append(hs, h)
switch len(hs) {
case 0:
return nil, nil
case 1:
return hs[0], nil
}
return CredentialHelpers(hs), nil
}
// getCredentialConfig parses a *credsConfig given the OS and Git
// configurations.
func getCredentialConfig(o, g config.Environment) (*credsConfig, error) {
askpass, ok := o.Get("GIT_ASKPASS")
if !ok {
askpass, ok = g.Get("core.askpass")
}
if !ok {
askpass, ok = o.Get("SSH_ASKPASS")
}
helper, _ := g.Get("credential.helper")
what := &credsConfig{
AskPass: askpass,
Helper: helper,
Cached: g.Bool("lfs.cachecredentials", true),
SkipPrompt: o.Bool("GIT_TERMINAL_PROMPT", false),
}
return what, nil
}
// CredentialHelpers is a []CredentialHelper that iterates through each
// credential helper to fill, reject, or approve credentials.
type CredentialHelpers []CredentialHelper
// Fill implements CredentialHelper.Fill by asking each CredentialHelper in
// order to fill the credentials.
//
// If a fill was successful, it is returned immediately, and no other
// `CredentialHelper`s are consulted. If any CredentialHelper returns an error,
// it is returned immediately.
func (h CredentialHelpers) Fill(what Creds) (Creds, error) {
for _, c := range h {
creds, err := c.Fill(what)
if err != nil {
return nil, err
}
if creds != nil {
return creds, nil
if c.askpassCredHelper != nil {
helper, _ := c.uc.Get("credential", rawurl, "helper")
if len(helper) == 0 {
helpers = append(helpers, c.askpassCredHelper)
}
}
return nil, nil
}
// Reject implements CredentialHelper.Reject and rejects the given Creds "what"
// amongst all knonw CredentialHelpers. If any `CredentialHelper`s returned a
// non-nil error, no further `CredentialHelper`s are notified, so as to prevent
// inconsistent state.
func (h CredentialHelpers) Reject(what Creds) error {
for _, c := range h {
if err := c.Reject(what); err != nil {
return err
}
}
return nil
}
// Approve implements CredentialHelper.Approve and approves the given Creds
// "what" amongst all known CredentialHelpers. If any `CredentialHelper`s
// returned a non-nil error, no further `CredentialHelper`s are notified, so as
// to prevent inconsistent state.
func (h CredentialHelpers) Approve(what Creds) error {
for _, c := range h {
if err := c.Approve(what); err != nil {
return err
}
}
return nil
return NewCredentialHelpers(append(helpers, c.commandCredHelper)), input
}
// AskPassCredentialHelper implements the CredentialHelper type for GIT_ASKPASS
@ -234,88 +162,6 @@ func (a *AskPassCredentialHelper) args(prompt string) []string {
return []string{prompt}
}
type CredentialHelper interface {
Fill(Creds) (Creds, error)
Reject(Creds) error
Approve(Creds) error
}
type Creds map[string]string
func bufferCreds(c Creds) *bytes.Buffer {
buf := new(bytes.Buffer)
for k, v := range c {
buf.Write([]byte(k))
buf.Write([]byte("="))
buf.Write([]byte(v))
buf.Write([]byte("\n"))
}
return buf
}
func withCredentialCache(helper CredentialHelper) CredentialHelper {
return &credentialCacher{
cmu: new(sync.Mutex),
creds: make(map[string]Creds),
helper: helper,
}
}
type credentialCacher struct {
// cmu guards creds
cmu *sync.Mutex
creds map[string]Creds
helper CredentialHelper
}
func credCacheKey(creds Creds) string {
parts := []string{
creds["protocol"],
creds["host"],
creds["path"],
}
return strings.Join(parts, "//")
}
func (c *credentialCacher) Fill(creds Creds) (Creds, error) {
key := credCacheKey(creds)
c.cmu.Lock()
defer c.cmu.Unlock()
if cache, ok := c.creds[key]; ok {
tracerx.Printf("creds: git credential cache (%q, %q, %q)",
creds["protocol"], creds["host"], creds["path"])
return cache, nil
}
creds, err := c.helper.Fill(creds)
if err == nil && len(creds["username"]) > 0 && len(creds["password"]) > 0 {
c.creds[key] = creds
}
return creds, err
}
func (c *credentialCacher) Reject(creds Creds) error {
c.cmu.Lock()
defer c.cmu.Unlock()
delete(c.creds, credCacheKey(creds))
return c.helper.Reject(creds)
}
func (c *credentialCacher) Approve(creds Creds) error {
err := c.helper.Approve(creds)
if err == nil {
c.cmu.Lock()
c.creds[credCacheKey(creds)] = creds
c.cmu.Unlock()
}
return err
}
type commandCredentialHelper struct {
SkipPrompt bool
}
@ -332,6 +178,8 @@ func (h *commandCredentialHelper) Reject(creds Creds) error {
}
func (h *commandCredentialHelper) Approve(creds Creds) error {
tracerx.Printf("creds: git credential approve (%q, %q, %q)",
creds["protocol"], creds["host"], creds["path"])
_, err := h.exec("approve", creds)
return err
}
@ -383,3 +231,198 @@ func (h *commandCredentialHelper) exec(subcommand string, input Creds) (Creds, e
return creds, nil
}
type credentialCacher struct {
creds map[string]Creds
mu sync.Mutex
}
func newCredentialCacher() *credentialCacher {
return &credentialCacher{creds: make(map[string]Creds)}
}
func credCacheKey(creds Creds) string {
parts := []string{
creds["protocol"],
creds["host"],
creds["path"],
}
return strings.Join(parts, "//")
}
func (c *credentialCacher) Fill(what Creds) (Creds, error) {
key := credCacheKey(what)
c.mu.Lock()
cached, ok := c.creds[key]
c.mu.Unlock()
if ok {
tracerx.Printf("creds: git credential cache (%q, %q, %q)",
what["protocol"], what["host"], what["path"])
return cached, nil
}
return nil, credHelperNoOp
}
func (c *credentialCacher) Approve(what Creds) error {
key := credCacheKey(what)
c.mu.Lock()
defer c.mu.Unlock()
if _, ok := c.creds[key]; ok {
return nil
}
c.creds[key] = what
return credHelperNoOp
}
func (c *credentialCacher) Reject(what Creds) error {
key := credCacheKey(what)
c.mu.Lock()
delete(c.creds, key)
c.mu.Unlock()
return credHelperNoOp
}
// CredentialHelpers iterates through a slice of CredentialHelper objects
// CredentialHelpers is a []CredentialHelper that iterates through each
// credential helper to fill, reject, or approve credentials. Typically, the
// first success returns immediately. Errors are reported to tracerx, unless
// all credential helpers return errors. Any erroring credential helpers are
// skipped for future calls.
//
// A CredentialHelper can return a credHelperNoOp error, signaling that the
// CredentialHelpers should try the next one.
type CredentialHelpers struct {
helpers []CredentialHelper
skippedHelpers map[int]bool
mu sync.Mutex
}
// NewCredentialHelpers initializes a new CredentialHelpers from the given
// slice of CredentialHelper instances.
func NewCredentialHelpers(helpers []CredentialHelper) CredentialHelper {
return &CredentialHelpers{
helpers: helpers,
skippedHelpers: make(map[int]bool),
}
}
var credHelperNoOp = errors.New("no-op!")
// Fill implements CredentialHelper.Fill by asking each CredentialHelper in
// order to fill the credentials.
//
// If a fill was successful, it is returned immediately, and no other
// `CredentialHelper`s are consulted. If any CredentialHelper returns an error,
// it is reported to tracerx, and the next one is attempted. If they all error,
// then a collection of all the error messages is returned. Erroring credential
// helpers are added to the skip list, and never attempted again for the
// lifetime of the current Git LFS command.
func (s *CredentialHelpers) Fill(what Creds) (Creds, error) {
errs := make([]string, 0, len(s.helpers))
for i, h := range s.helpers {
if s.skipped(i) {
continue
}
creds, err := h.Fill(what)
if err != nil {
if err != credHelperNoOp {
s.skip(i)
tracerx.Printf("credential fill error: %s", err)
errs = append(errs, err.Error())
}
continue
}
if creds != nil {
return creds, nil
}
}
if len(errs) > 0 {
return nil, errors.New("credential fill errors:\n" + strings.Join(errs, "\n"))
}
return nil, nil
}
// Reject implements CredentialHelper.Reject and rejects the given Creds "what"
// with the first successful attempt.
func (s *CredentialHelpers) Reject(what Creds) error {
for i, h := range s.helpers {
if s.skipped(i) {
continue
}
if err := h.Reject(what); err != credHelperNoOp {
return err
}
}
return errors.New("no valid credential helpers to reject")
}
// Approve implements CredentialHelper.Approve and approves the given Creds
// "what" with the first successful CredentialHelper. If an error occurrs,
// it calls Reject() with the same Creds and returns the error immediately. This
// ensures a caching credential helper removes the cache, since the Erroring
// CredentialHelper never successfully saved it.
func (s *CredentialHelpers) Approve(what Creds) error {
skipped := make(map[int]bool)
for i, h := range s.helpers {
if s.skipped(i) {
skipped[i] = true
continue
}
if err := h.Approve(what); err != credHelperNoOp {
if err != nil && i > 0 { // clear any cached approvals
for j := 0; j < i; j++ {
if !skipped[j] {
s.helpers[j].Reject(what)
}
}
}
return err
}
}
return errors.New("no valid credential helpers to approve")
}
func (s *CredentialHelpers) skip(i int) {
s.mu.Lock()
s.skippedHelpers[i] = true
s.mu.Unlock()
}
func (s *CredentialHelpers) skipped(i int) bool {
s.mu.Lock()
skipped := s.skippedHelpers[i]
s.mu.Unlock()
return skipped
}
type nullCredentialHelper struct{}
var (
nullCredError = errors.New("No credential helper configured")
nullCreds = &nullCredentialHelper{}
)
func (h *nullCredentialHelper) Fill(input Creds) (Creds, error) {
return nil, nullCredError
}
func (h *nullCredentialHelper) Approve(creds Creds) error {
return nil
}
func (h *nullCredentialHelper) Reject(creds Creds) error {
return nil
}

@ -5,264 +5,262 @@ import (
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
// test that cache satisfies Fill() without looking at creds
func TestCredsCacheFillFromCache(t *testing.T) {
creds := newFakeCreds()
cache := withCredentialCache(creds).(*credentialCacher)
cache.creds["http//lfs.test//foo/bar"] = Creds{
"protocol": "http",
"host": "lfs.test",
"path": "foo/bar",
"username": "u",
"password": "p",
type testCredHelper struct {
fillErr error
approveErr error
rejectErr error
fill []Creds
approve []Creds
reject []Creds
}
filled, err := cache.Fill(Creds{
"protocol": "http",
"host": "lfs.test",
"path": "foo/bar",
})
func newTestCredHelper() *testCredHelper {
return &testCredHelper{
fill: make([]Creds, 0),
approve: make([]Creds, 0),
reject: make([]Creds, 0),
}
}
func (h *testCredHelper) Fill(input Creds) (Creds, error) {
h.fill = append(h.fill, input)
return input, h.fillErr
}
func (h *testCredHelper) Approve(creds Creds) error {
h.approve = append(h.approve, creds)
return h.approveErr
}
func (h *testCredHelper) Reject(creds Creds) error {
h.reject = append(h.reject, creds)
return h.rejectErr
}
func TestCredHelperSetNoErrors(t *testing.T) {
cache := newCredentialCacher()
helper1 := newTestCredHelper()
helper2 := newTestCredHelper()
helpers := NewCredentialHelpers([]CredentialHelper{cache, helper1, helper2})
creds := Creds{"protocol": "https", "host": "example.com"}
out, err := helpers.Fill(creds)
assert.Nil(t, err)
require.NotNil(t, filled)
assert.Equal(t, "u", filled["username"])
assert.Equal(t, "p", filled["password"])
assert.Equal(t, creds, out)
assert.Equal(t, 1, len(helper1.fill))
assert.Equal(t, 0, len(helper2.fill))
assert.Equal(t, 1, len(cache.creds))
cached, ok := cache.creds["http//lfs.test//foo/bar"]
assert.True(t, ok)
assert.Equal(t, "u", cached["username"])
assert.Equal(t, "p", cached["password"])
}
// test that cache caches Fill() value from creds
func TestCredsCacheFillFromValidHelperFill(t *testing.T) {
creds := newFakeCreds()
cache := withCredentialCache(creds).(*credentialCacher)
creds.list = append(creds.list, Creds{
"protocol": "http",
"host": "lfs.test",
"path": "foo/bar",
"username": "u",
"password": "p",
})
assert.Equal(t, 0, len(cache.creds))
filled, err := cache.Fill(Creds{
"protocol": "http",
"host": "lfs.test",
"path": "foo/bar",
})
// calling Fill() with empty cache
out, err = helpers.Fill(creds)
assert.Nil(t, err)
require.NotNil(t, filled)
assert.Equal(t, "u", filled["username"])
assert.Equal(t, "p", filled["password"])
assert.Equal(t, creds, out)
assert.Equal(t, 2, len(helper1.fill))
assert.Equal(t, 0, len(helper2.fill))
assert.Equal(t, 1, len(cache.creds))
cached, ok := cache.creds["http//lfs.test//foo/bar"]
assert.True(t, ok)
assert.Equal(t, "u", cached["username"])
assert.Equal(t, "p", cached["password"])
credsWithPass := Creds{"protocol": "https", "host": "example.com", "username": "foo", "password": "bar"}
assert.Nil(t, helpers.Approve(credsWithPass))
assert.Equal(t, 1, len(helper1.approve))
assert.Equal(t, 0, len(helper2.approve))
creds.list = make([]Creds, 0)
filled2, err := cache.Fill(Creds{
"protocol": "http",
"host": "lfs.test",
"path": "foo/bar",
})
// calling Approve() again is cached
assert.Nil(t, helpers.Approve(credsWithPass))
assert.Equal(t, 1, len(helper1.approve))
assert.Equal(t, 0, len(helper2.approve))
// access cache
for i := 0; i < 3; i++ {
out, err = helpers.Fill(creds)
assert.Nil(t, err)
require.NotNil(t, filled2)
assert.Equal(t, "u", filled2["username"])
assert.Equal(t, "p", filled2["password"])
assert.Equal(t, credsWithPass, out)
assert.Equal(t, 2, len(helper1.fill))
assert.Equal(t, 0, len(helper2.fill))
}
// test that cache ignores Fill() value from creds with missing username+password
func TestCredsCacheFillFromInvalidHelperFill(t *testing.T) {
creds := newFakeCreds()
cache := withCredentialCache(creds).(*credentialCacher)
assert.Nil(t, helpers.Reject(creds))
assert.Equal(t, 1, len(helper1.reject))
assert.Equal(t, 0, len(helper2.reject))
creds.list = append(creds.list, Creds{
"protocol": "http",
"host": "lfs.test",
"path": "foo/bar",
"username": "no-password",
})
// Reject() is never cached
assert.Nil(t, helpers.Reject(creds))
assert.Equal(t, 2, len(helper1.reject))
assert.Equal(t, 0, len(helper2.reject))
assert.Equal(t, 0, len(cache.creds))
filled, err := cache.Fill(Creds{
"protocol": "http",
"host": "lfs.test",
"path": "foo/bar",
"username": "u",
"password": "p",
})
// calling Fill() with empty cache
out, err = helpers.Fill(creds)
assert.Nil(t, err)
require.NotNil(t, filled)
assert.Equal(t, "no-password", filled["username"])
assert.Equal(t, "", filled["password"])
assert.Equal(t, 0, len(cache.creds))
assert.Equal(t, creds, out)
assert.Equal(t, 3, len(helper1.fill))
assert.Equal(t, 0, len(helper2.fill))
}
// test that cache ignores Fill() value from creds with error
func TestCredsCacheFillFromErroringHelperFill(t *testing.T) {
creds := newFakeCreds()
cache := withCredentialCache(&erroringCreds{creds}).(*credentialCacher)
func TestCredHelperSetFillError(t *testing.T) {
cache := newCredentialCacher()
helper1 := newTestCredHelper()
helper2 := newTestCredHelper()
helpers := NewCredentialHelpers([]CredentialHelper{cache, helper1, helper2})
creds := Creds{"protocol": "https", "host": "example.com"}
creds.list = append(creds.list, Creds{
"protocol": "http",
"host": "lfs.test",
"path": "foo/bar",
"username": "u",
"password": "p",
})
assert.Equal(t, 0, len(cache.creds))
filled, err := cache.Fill(Creds{
"protocol": "http",
"host": "lfs.test",
"path": "foo/bar",
})
assert.NotNil(t, err)
require.NotNil(t, filled)
assert.Equal(t, "u", filled["username"])
assert.Equal(t, "p", filled["password"])
assert.Equal(t, 0, len(cache.creds))
}
func TestCredsCacheRejectWithoutError(t *testing.T) {
creds := newFakeCreds()
cache := withCredentialCache(creds).(*credentialCacher)
cache.creds["http//lfs.test//foo/bar"] = Creds{
"protocol": "http",
"host": "lfs.test",
"path": "foo/bar",
"username": "u",
"password": "p",
}
err := cache.Reject(Creds{
"protocol": "http",
"host": "lfs.test",
"path": "foo/bar",
})
helper1.fillErr = errors.New("boom")
out, err := helpers.Fill(creds)
assert.Nil(t, err)
assert.Equal(t, 0, len(cache.creds))
}
assert.Equal(t, creds, out)
assert.Equal(t, 1, len(helper1.fill))
assert.Equal(t, 1, len(helper2.fill))
func TestCredsCacheRejectWithError(t *testing.T) {
creds := newFakeCreds()
cache := withCredentialCache(&erroringCreds{creds}).(*credentialCacher)
assert.Nil(t, helpers.Approve(creds))
assert.Equal(t, 0, len(helper1.approve))
assert.Equal(t, 1, len(helper2.approve))
cache.creds["http//lfs.test//foo/bar"] = Creds{
"protocol": "http",
"host": "lfs.test",
"path": "foo/bar",
"username": "u",
"password": "p",
}
err := cache.Reject(Creds{
"protocol": "http",
"host": "lfs.test",
"path": "foo/bar",
})
assert.NotNil(t, err)
assert.Equal(t, 0, len(cache.creds))
}
func TestCredsCacheApproveWithoutError(t *testing.T) {
creds := newFakeCreds()
cache := withCredentialCache(creds).(*credentialCacher)
assert.Equal(t, 0, len(cache.creds))
err := cache.Approve(Creds{
"protocol": "http",
"host": "lfs.test",
"path": "foo/bar",
"username": "U",
"password": "P",
})
// Fill() with cache
for i := 0; i < 3; i++ {
out, err = helpers.Fill(creds)
assert.Nil(t, err)
assert.Equal(t, 1, len(cache.creds))
cached, ok := cache.creds["http//lfs.test//foo/bar"]
assert.True(t, ok)
assert.Equal(t, "U", cached["username"])
assert.Equal(t, "P", cached["password"])
assert.Equal(t, creds, out)
assert.Equal(t, 1, len(helper1.fill))
assert.Equal(t, 1, len(helper2.fill))
}
func TestCredsCacheApproveWithError(t *testing.T) {
creds := newFakeCreds()
cache := withCredentialCache(&erroringCreds{creds}).(*credentialCacher)
assert.Nil(t, helpers.Reject(creds))
assert.Equal(t, 0, len(helper1.reject))
assert.Equal(t, 1, len(helper2.reject))
assert.Equal(t, 0, len(cache.creds))
err := cache.Approve(Creds{
"protocol": "http",
"host": "lfs.test",
"path": "foo/bar",
"username": "u",
"password": "p",
})
assert.NotNil(t, err)
assert.Equal(t, 0, len(cache.creds))
// Fill() with empty cache
out, err = helpers.Fill(creds)
assert.Nil(t, err)
assert.Equal(t, creds, out)
assert.Equal(t, 1, len(helper1.fill)) // still skipped
assert.Equal(t, 2, len(helper2.fill))
}
func newFakeCreds() *fakeCreds {
return &fakeCreds{list: make([]Creds, 0)}
func TestCredHelperSetApproveError(t *testing.T) {
cache := newCredentialCacher()
helper1 := newTestCredHelper()
helper2 := newTestCredHelper()
helpers := NewCredentialHelpers([]CredentialHelper{cache, helper1, helper2})
creds := Creds{"protocol": "https", "host": "example.com"}
approveErr := errors.New("boom")
helper1.approveErr = approveErr
out, err := helpers.Fill(creds)
assert.Nil(t, err)
assert.Equal(t, creds, out)
assert.Equal(t, 1, len(helper1.fill))
assert.Equal(t, 0, len(helper2.fill))
assert.Equal(t, approveErr, helpers.Approve(creds))
assert.Equal(t, 1, len(helper1.approve))
assert.Equal(t, 0, len(helper2.approve))
// cache is never set
out, err = helpers.Fill(creds)
assert.Nil(t, err)
assert.Equal(t, creds, out)
assert.Equal(t, 2, len(helper1.fill))
assert.Equal(t, 0, len(helper2.fill))
assert.Nil(t, helpers.Reject(creds))
assert.Equal(t, 1, len(helper1.reject))
assert.Equal(t, 0, len(helper2.reject))
}
type erroringCreds struct {
helper CredentialHelper
func TestCredHelperSetFillAndApproveError(t *testing.T) {
cache := newCredentialCacher()
helper1 := newTestCredHelper()
helper2 := newTestCredHelper()
helpers := NewCredentialHelpers([]CredentialHelper{cache, helper1, helper2})
creds := Creds{"protocol": "https", "host": "example.com"}
credErr := errors.New("boom")
helper1.fillErr = credErr
helper2.approveErr = credErr
out, err := helpers.Fill(creds)
assert.Nil(t, err)
assert.Equal(t, creds, out)
assert.Equal(t, 1, len(helper1.fill))
assert.Equal(t, 1, len(helper2.fill))
assert.Equal(t, credErr, helpers.Approve(creds))
assert.Equal(t, 0, len(helper1.approve)) // skipped
assert.Equal(t, 0, len(helper1.reject)) // skipped
assert.Equal(t, 1, len(helper2.approve))
// never approved, so cache is empty
out, err = helpers.Fill(creds)
assert.Nil(t, err)
assert.Equal(t, creds, out)
assert.Equal(t, 1, len(helper1.fill)) // still skipped
assert.Equal(t, 2, len(helper2.fill))
}
func (e *erroringCreds) Fill(creds Creds) (Creds, error) {
c, _ := e.helper.Fill(creds)
return c, errors.New("fill error")
func TestCredHelperSetRejectError(t *testing.T) {
cache := newCredentialCacher()
helper1 := newTestCredHelper()
helper2 := newTestCredHelper()
helpers := NewCredentialHelpers([]CredentialHelper{cache, helper1, helper2})
creds := Creds{"protocol": "https", "host": "example.com"}
rejectErr := errors.New("boom")
helper1.rejectErr = rejectErr
out, err := helpers.Fill(creds)
assert.Nil(t, err)
assert.Equal(t, creds, out)
assert.Equal(t, 1, len(helper1.fill))
assert.Equal(t, 0, len(helper2.fill))
assert.Nil(t, helpers.Approve(creds))
assert.Equal(t, 1, len(helper1.approve))
assert.Equal(t, 0, len(helper2.approve))
// Fill() with cache
out, err = helpers.Fill(creds)
assert.Nil(t, err)
assert.Equal(t, creds, out)
assert.Equal(t, 1, len(helper1.fill))
assert.Equal(t, 0, len(helper2.fill))
assert.Equal(t, rejectErr, helpers.Reject(creds))
assert.Equal(t, 1, len(helper1.reject))
assert.Equal(t, 0, len(helper2.reject))
// failed Reject() still clears cache
out, err = helpers.Fill(creds)
assert.Nil(t, err)
assert.Equal(t, creds, out)
assert.Equal(t, 2, len(helper1.fill))
assert.Equal(t, 0, len(helper2.fill))
}
func (e *erroringCreds) Reject(creds Creds) error {
e.helper.Reject(creds)
return errors.New("reject error")
}
func TestCredHelperSetAllFillErrors(t *testing.T) {
cache := newCredentialCacher()
helper1 := newTestCredHelper()
helper2 := newTestCredHelper()
helpers := NewCredentialHelpers([]CredentialHelper{cache, helper1, helper2})
creds := Creds{"protocol": "https", "host": "example.com"}
func (e *erroringCreds) Approve(creds Creds) error {
e.helper.Approve(creds)
return errors.New("approve error")
helper1.fillErr = errors.New("boom 1")
helper2.fillErr = errors.New("boom 2")
out, err := helpers.Fill(creds)
if assert.NotNil(t, err) {
assert.Equal(t, "credential fill errors:\nboom 1\nboom 2", err.Error())
}
assert.Nil(t, out)
assert.Equal(t, 1, len(helper1.fill))
assert.Equal(t, 1, len(helper2.fill))
type fakeCreds struct {
list []Creds
err = helpers.Approve(creds)
if assert.NotNil(t, err) {
assert.Equal(t, "no valid credential helpers to approve", err.Error())
}
assert.Equal(t, 0, len(helper1.approve))
assert.Equal(t, 0, len(helper2.approve))
func credsMatch(c1, c2 Creds) bool {
return c1["protocol"] == c2["protocol"] &&
c1["host"] == c2["host"] &&
c1["path"] == c2["path"]
err = helpers.Reject(creds)
if assert.NotNil(t, err) {
assert.Equal(t, "no valid credential helpers to reject", err.Error())
}
func (f *fakeCreds) Fill(creds Creds) (Creds, error) {
for _, saved := range f.list {
if credsMatch(creds, saved) {
return saved, nil
}
}
return creds, nil
}
func (f *fakeCreds) Reject(creds Creds) error {
return nil
}
func (f *fakeCreds) Approve(creds Creds) error {
return nil
assert.Equal(t, 0, len(helper1.reject))
assert.Equal(t, 0, len(helper2.reject))
}

@ -6,8 +6,6 @@ import (
"io"
"net/http"
"regexp"
"strconv"
"strings"
"sync"
"github.com/ThomsonReutersEikon/go-ntlm/ntlm"
@ -47,7 +45,9 @@ type Client struct {
LoggingStats bool // DEPRECATED
// only used for per-host ssl certs
commandCredHelper *commandCredentialHelper
askpassCredHelper *AskPassCredentialHelper
cachingCredHelper *credentialCacher
gitEnv config.Environment
osEnv config.Environment
uc *config.URLConfig
@ -71,19 +71,14 @@ func NewClient(ctx Context) (*Client, error) {
return nil, errors.Wrap(err, fmt.Sprintf("bad netrc file %s", netrcfile))
}
creds, err := getCredentialHelper(osEnv, gitEnv)
if err != nil {
return nil, errors.Wrap(err, "cannot find credential helper(s)")
}
cacheCreds := gitEnv.Bool("lfs.cachecredentials", true)
var sshResolver SSHResolver = &sshAuthClient{os: osEnv}
if gitEnv.Bool("lfs.cachecredentials", true) {
if cacheCreds {
sshResolver = withSSHCache(sshResolver)
}
c := &Client{
Endpoints: NewEndpointFinder(ctx),
Credentials: creds,
SSH: sshResolver,
Netrc: netrc,
DialTimeout: gitEnv.Int("lfs.dialtimeout", 0),
@ -93,11 +88,31 @@ func NewClient(ctx Context) (*Client, error) {
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{
SkipPrompt: osEnv.Bool("GIT_TERMINAL_PROMPT", false),
},
gitEnv: gitEnv,
osEnv: osEnv,
uc: config.NewURLConfig(gitEnv),
}
askpass, ok := osEnv.Get("GIT_ASKPASS")
if !ok {
askpass, ok = gitEnv.Get("core.askpass")
}
if !ok {
askpass, _ = osEnv.Get("SSH_ASKPASS")
}
if len(askpass) > 0 {
c.askpassCredHelper = &AskPassCredentialHelper{
Program: askpass,
}
}
if cacheCreds {
c.cachingCredHelper = newCredentialCacher()
}
return c, nil
}
@ -191,34 +206,14 @@ func (e testEnv) GetAll(key string) []string {
return make([]string, 0)
}
func (e testEnv) Int(key string, def int) (val int) {
func (e testEnv) Int(key string, def int) int {
s, _ := e.Get(key)
if len(s) == 0 {
return def
return config.Int(s, def)
}
i, err := strconv.Atoi(s)
if err != nil {
return def
}
return i
}
func (e testEnv) Bool(key string, def bool) (val bool) {
func (e testEnv) Bool(key string, def bool) bool {
s, _ := e.Get(key)
if len(s) == 0 {
return def
}
switch strings.ToLower(s) {
case "true", "1", "on", "yes", "t":
return true
case "false", "0", "off", "no", "f":
return false
default:
return false
}
return config.Bool(s, def)
}
func (e testEnv) All() map[string][]string {

@ -90,7 +90,6 @@ func TestNTLMAuth(t *testing.T) {
creds := Creds{
"protocol": srvURL.Scheme,
"host": srvURL.Host,
"path": "ntlm",
"username": "ntlmdomain\\ntlmuser",
"password": "ntlmpass",
}

@ -28,3 +28,25 @@ begin_test "attempt private access without credential helper"
grep "Git credentials for $GITSERVER/$reponame not found" push.log
)
end_test
begin_test "askpass: push with bad askpass"
(
set -e
reponame="askpass-with-bad-askpass"
setup_remote_repo "$reponame"
clone_repo "$reponame" "$reponame"
git lfs track "*.dat"
echo "hello" > a.dat
git add .gitattributes a.dat
git commit -m "initial commit"
git config "credential.helper" ""
GIT_TERMINAL_PROMPT=0 GIT_ASKPASS="lfs-askpass-2" SSH_ASKPASS="dont-call-me" GIT_TRACE=1 git push origin master 2>&1 | tee push.log
grep "filling with GIT_ASKPASS" push.log # attempt askpass
grep 'credential fill error: exec: "lfs-askpass-2"' push.log # askpass fails
grep "creds: git credential fill" push.log # attempt git credential
)
end_test

@ -4,11 +4,36 @@
ensure_git_version_isnt $VERSION_LOWER "2.3.0"
begin_test "credentails with url-specific helper skips askpass"
(
set -e
reponame="url-specific-helper"
setup_remote_repo "$reponame"
clone_repo "$reponame" "$reponame"
git config credential.useHttpPath false
git config credential.helper ""
git config credential.$GITSERVER.helper "lfstest"
git lfs track "*.dat"
echo "hello" > a.dat
git add .gitattributes a.dat
git commit -m "initial commit"
# askpass is skipped
GIT_ASKPASS="lfs-bad-cmd" GIT_TRACE=1 git push origin master 2>&1 | tee push.log
[ "0" -eq "$(grep "filling with GIT_ASKPASS" push.log | wc -l)" ]
)
end_test
begin_test "credentials without useHttpPath, with bad path password"
(
set -e
reponame="$(basename "$0" ".sh")"
reponame="no-httppath-bad-password"
setup_remote_repo "$reponame"
printf "path:wrong" > "$CREDSDIR/127.0.0.1--$reponame"
@ -20,16 +45,55 @@ begin_test "credentials without useHttpPath, with bad path password"
git lfs track "*.dat" 2>&1 | tee track.log
grep "Tracking \"\*.dat\"" track.log
contents="a"
contents_oid=$(calc_oid "$contents")
printf "$contents" > a.dat
printf "a" > a.dat
git add a.dat
git add .gitattributes
git commit -m "add a.dat"
git push origin without-path 2>&1 | tee push.log
GIT_TRACE=1 git push origin without-path 2>&1 | tee push.log
grep "(1 of 1 files)" push.log
echo "approvals:"
[ "1" -eq "$(cat push.log | grep "creds: git credential approve" | wc -l)" ]
echo "fills:"
[ "1" -eq "$(cat push.log | grep "creds: git credential fill" | wc -l)" ]
echo "credential calls have no path:"
credcalls="$(grep "creds: git credential" push.log)"
[ "0" -eq "$(echo "$credcalls" | grep "no-httppath-bad-password" | wc -l)" ]
expected="$(echo "$credcalls" | wc -l)"
[ "$expected" -eq "$(printf "$credcalls" | grep '", "")' | wc -l)" ]
)
end_test
begin_test "credentials with url-specific useHttpPath, with bad path password"
(
set -e
reponame="url-specific-httppath-bad-password"
setup_remote_repo "$reponame"
printf "path:wrong" > "$CREDSDIR/127.0.0.1--$reponame"
clone_repo "$reponame" with-url-specific-path
git config credential.$GITSERVER.useHttpPath false
git checkout -b without-path
git lfs track "*.dat" 2>&1 | tee track.log
grep "Tracking \"\*.dat\"" track.log
printf "a" > a.dat
git add a.dat
git add .gitattributes
git commit -m "add a.dat"
GIT_TRACE=1 git push origin without-path 2>&1 | tee push.log
grep "(1 of 1 files)" push.log
echo "approvals:"
[ "1" -eq "$(cat push.log | grep "creds: git credential approve" | wc -l)" ]
echo "fills:"
[ "1" -eq "$(cat push.log | grep "creds: git credential fill" | wc -l)" ]
)
end_test
@ -37,7 +101,7 @@ begin_test "credentials with useHttpPath, with wrong password"
(
set -e
reponame="$(basename "$0" ".sh")"
reponame="httppath-bad-password"
setup_remote_repo "$reponame"
printf "path:wrong" > "$CREDSDIR/127.0.0.1--$reponame"
@ -56,8 +120,12 @@ begin_test "credentials with useHttpPath, with wrong password"
git add .gitattributes
git commit -m "add a.dat"
git push origin with-path-wrong-pass 2>&1 | tee push.log
GIT_TRACE=1 git push origin with-path-wrong-pass 2>&1 | tee push.log
[ "0" = "$(grep -c "(1 of 1 files)" push.log)" ]
echo "approvals:"
[ "0" -eq "$(cat push.log | grep "creds: git credential approve" | wc -l)" ]
echo "fills:"
[ "2" -eq "$(cat push.log | grep "creds: git credential fill" | wc -l)" ]
)
end_test
@ -86,8 +154,17 @@ begin_test "credentials with useHttpPath, with correct password"
git add .gitattributes
git commit -m "add b.dat"
git push origin with-path-correct-pass 2>&1 | tee push.log
GIT_TRACE=1 git push origin with-path-correct-pass 2>&1 | tee push.log
grep "(1 of 1 files)" push.log
echo "approvals:"
[ "1" -eq "$(cat push.log | grep "creds: git credential approve" | wc -l)" ]
echo "fills:"
[ "1" -eq "$(cat push.log | grep "creds: git credential fill" | wc -l)" ]
echo "credential calls have path:"
credcalls="$(grep "creds: git credential" push.log)"
[ "0" -eq "$(echo "$credcalls" | grep '", "")' | wc -l)" ]
expected="$(echo "$credcalls" | wc -l)"
[ "$expected" -eq "$(printf "$credcalls" | grep "test-credentials" | wc -l)" ]
)
end_test
@ -175,8 +252,10 @@ begin_test "credentials from netrc"
git add .gitattributes a.dat
git commit -m "add a.dat"
git lfs push netrc master 2>&1 | tee push.log
GIT_TRACE=1 git lfs push netrc master 2>&1 | tee push.log
grep "(1 of 1 files)" push.log
echo "any git credential calls:"
[ "0" -eq "$(cat push.log | grep "git credential" | wc -l)" ]
)
end_test

@ -47,7 +47,6 @@ for typ in "${expiration_types[@]}"; do
sshurl="${GITSERVER/http:\/\//ssh://git@}/$reponame"
git config lfs.url "$sshurl"
git config lfs.cachecredentials "true"
contents="contents"
contents_oid="$(calc_oid "$contents")"