github.com/stffabi/git-lfs@v2.3.5-0.20180214015214-8eeaa8d88902+incompatible/lfsapi/endpoint_finder.go (about)

     1  package lfsapi
     2  
     3  import (
     4  	"fmt"
     5  	"net/url"
     6  	"os"
     7  	"path"
     8  	"strings"
     9  	"sync"
    10  
    11  	"github.com/git-lfs/git-lfs/config"
    12  	"github.com/git-lfs/git-lfs/git"
    13  	"github.com/rubyist/tracerx"
    14  )
    15  
    16  type Access string
    17  
    18  const (
    19  	NoneAccess      Access = "none"
    20  	BasicAccess     Access = "basic"
    21  	PrivateAccess   Access = "private"
    22  	NegotiateAccess Access = "negotiate"
    23  	NTLMAccess      Access = "ntlm"
    24  	emptyAccess     Access = ""
    25  	defaultRemote          = "origin"
    26  )
    27  
    28  type EndpointFinder interface {
    29  	NewEndpointFromCloneURL(rawurl string) Endpoint
    30  	NewEndpoint(rawurl string) Endpoint
    31  	Endpoint(operation, remote string) Endpoint
    32  	RemoteEndpoint(operation, remote string) Endpoint
    33  	GitRemoteURL(remote string, forpush bool) string
    34  	AccessFor(rawurl string) Access
    35  	SetAccess(rawurl string, access Access)
    36  	GitProtocol() string
    37  }
    38  
    39  type endpointGitFinder struct {
    40  	gitConfig   *git.Configuration
    41  	gitEnv      config.Environment
    42  	gitProtocol string
    43  
    44  	aliasMu sync.Mutex
    45  	aliases map[string]string
    46  
    47  	accessMu  sync.Mutex
    48  	urlAccess map[string]Access
    49  	urlConfig *config.URLConfig
    50  }
    51  
    52  func NewEndpointFinder(ctx Context) EndpointFinder {
    53  	if ctx == nil {
    54  		ctx = NewContext(nil, nil, nil)
    55  	}
    56  
    57  	e := &endpointGitFinder{
    58  		gitConfig:   ctx.GitConfig(),
    59  		gitEnv:      ctx.GitEnv(),
    60  		gitProtocol: "https",
    61  		aliases:     make(map[string]string),
    62  		urlAccess:   make(map[string]Access),
    63  	}
    64  
    65  	e.urlConfig = config.NewURLConfig(e.gitEnv)
    66  	if v, ok := e.gitEnv.Get("lfs.gitprotocol"); ok {
    67  		e.gitProtocol = v
    68  	}
    69  	initAliases(e, e.gitEnv)
    70  
    71  	return e
    72  }
    73  
    74  func (e *endpointGitFinder) Endpoint(operation, remote string) Endpoint {
    75  	ep := e.getEndpoint(operation, remote)
    76  	ep.Operation = operation
    77  	return ep
    78  }
    79  
    80  func (e *endpointGitFinder) getEndpoint(operation, remote string) Endpoint {
    81  	if e.gitEnv == nil {
    82  		return Endpoint{}
    83  	}
    84  
    85  	if operation == "upload" {
    86  		if url, ok := e.gitEnv.Get("lfs.pushurl"); ok {
    87  			return e.NewEndpoint(url)
    88  		}
    89  	}
    90  
    91  	if url, ok := e.gitEnv.Get("lfs.url"); ok {
    92  		return e.NewEndpoint(url)
    93  	}
    94  
    95  	if len(remote) > 0 && remote != defaultRemote {
    96  		if e := e.RemoteEndpoint(operation, remote); len(e.Url) > 0 {
    97  			return e
    98  		}
    99  	}
   100  
   101  	return e.RemoteEndpoint(operation, defaultRemote)
   102  }
   103  
   104  func (e *endpointGitFinder) RemoteEndpoint(operation, remote string) Endpoint {
   105  	if e.gitEnv == nil {
   106  		return Endpoint{}
   107  	}
   108  
   109  	if len(remote) == 0 {
   110  		remote = defaultRemote
   111  	}
   112  
   113  	// Support separate push URL if specified and pushing
   114  	if operation == "upload" {
   115  		if url, ok := e.gitEnv.Get("remote." + remote + ".lfspushurl"); ok {
   116  			return e.NewEndpoint(url)
   117  		}
   118  	}
   119  	if url, ok := e.gitEnv.Get("remote." + remote + ".lfsurl"); ok {
   120  		return e.NewEndpoint(url)
   121  	}
   122  
   123  	// finally fall back on git remote url (also supports pushurl)
   124  	if url := e.GitRemoteURL(remote, operation == "upload"); url != "" {
   125  		return e.NewEndpointFromCloneURL(url)
   126  	}
   127  
   128  	return Endpoint{}
   129  }
   130  
   131  func (e *endpointGitFinder) GitRemoteURL(remote string, forpush bool) string {
   132  	if e.gitEnv != nil {
   133  		if forpush {
   134  			if u, ok := e.gitEnv.Get("remote." + remote + ".pushurl"); ok {
   135  				return u
   136  			}
   137  		}
   138  
   139  		if u, ok := e.gitEnv.Get("remote." + remote + ".url"); ok {
   140  			return u
   141  		}
   142  	}
   143  
   144  	if err := git.ValidateRemote(remote); err == nil {
   145  		return remote
   146  	}
   147  
   148  	return ""
   149  }
   150  
   151  func (e *endpointGitFinder) NewEndpointFromCloneURL(rawurl string) Endpoint {
   152  	ep := e.NewEndpoint(rawurl)
   153  	if ep.Url == UrlUnknown {
   154  		return ep
   155  	}
   156  
   157  	if strings.HasSuffix(rawurl, "/") {
   158  		ep.Url = rawurl[0 : len(rawurl)-1]
   159  	}
   160  
   161  	// When using main remote URL for HTTP, append info/lfs
   162  	if path.Ext(ep.Url) == ".git" {
   163  		ep.Url += "/info/lfs"
   164  	} else {
   165  		ep.Url += ".git/info/lfs"
   166  	}
   167  
   168  	return ep
   169  }
   170  
   171  func (e *endpointGitFinder) NewEndpoint(rawurl string) Endpoint {
   172  	rawurl = e.ReplaceUrlAlias(rawurl)
   173  	u, err := url.Parse(rawurl)
   174  	if err != nil {
   175  		return endpointFromBareSshUrl(rawurl)
   176  	}
   177  
   178  	switch u.Scheme {
   179  	case "ssh":
   180  		return endpointFromSshUrl(u)
   181  	case "http", "https":
   182  		return endpointFromHttpUrl(u)
   183  	case "git":
   184  		return endpointFromGitUrl(u, e)
   185  	case "":
   186  		return endpointFromBareSshUrl(u.String())
   187  	default:
   188  		// Just passthrough to preserve
   189  		return Endpoint{Url: rawurl}
   190  	}
   191  }
   192  
   193  func (e *endpointGitFinder) AccessFor(rawurl string) Access {
   194  	if e.gitEnv == nil {
   195  		return NoneAccess
   196  	}
   197  
   198  	accessurl := urlWithoutAuth(rawurl)
   199  
   200  	e.accessMu.Lock()
   201  	defer e.accessMu.Unlock()
   202  
   203  	if cached, ok := e.urlAccess[accessurl]; ok {
   204  		return cached
   205  	}
   206  
   207  	e.urlAccess[accessurl] = e.fetchGitAccess(accessurl)
   208  	return e.urlAccess[accessurl]
   209  }
   210  
   211  func (e *endpointGitFinder) SetAccess(rawurl string, access Access) {
   212  	accessurl := urlWithoutAuth(rawurl)
   213  	key := fmt.Sprintf("lfs.%s.access", accessurl)
   214  	tracerx.Printf("setting repository access to %s", access)
   215  
   216  	e.accessMu.Lock()
   217  	defer e.accessMu.Unlock()
   218  
   219  	switch access {
   220  	case emptyAccess, NoneAccess:
   221  		e.gitConfig.UnsetLocalKey(key)
   222  		e.urlAccess[accessurl] = NoneAccess
   223  	default:
   224  		e.gitConfig.SetLocal(key, string(access))
   225  		e.urlAccess[accessurl] = access
   226  	}
   227  }
   228  
   229  func urlWithoutAuth(rawurl string) string {
   230  	if !strings.Contains(rawurl, "@") {
   231  		return rawurl
   232  	}
   233  
   234  	u, err := url.Parse(rawurl)
   235  	if err != nil {
   236  		fmt.Fprintf(os.Stderr, "Error parsing URL %q: %s", rawurl, err)
   237  		return rawurl
   238  	}
   239  
   240  	u.User = nil
   241  	return u.String()
   242  }
   243  
   244  func (e *endpointGitFinder) fetchGitAccess(rawurl string) Access {
   245  	if v, _ := e.urlConfig.Get("lfs", rawurl, "access"); len(v) > 0 {
   246  		access := Access(strings.ToLower(v))
   247  		if access == PrivateAccess {
   248  			return BasicAccess
   249  		}
   250  		return access
   251  	}
   252  	return NoneAccess
   253  }
   254  
   255  func (e *endpointGitFinder) GitProtocol() string {
   256  	return e.gitProtocol
   257  }
   258  
   259  // ReplaceUrlAlias returns a url with a prefix from a `url.*.insteadof` git
   260  // config setting. If multiple aliases match, use the longest one.
   261  // See https://git-scm.com/docs/git-config for Git's docs.
   262  func (e *endpointGitFinder) ReplaceUrlAlias(rawurl string) string {
   263  	e.aliasMu.Lock()
   264  	defer e.aliasMu.Unlock()
   265  
   266  	var longestalias string
   267  	for alias, _ := range e.aliases {
   268  		if !strings.HasPrefix(rawurl, alias) {
   269  			continue
   270  		}
   271  
   272  		if longestalias < alias {
   273  			longestalias = alias
   274  		}
   275  	}
   276  
   277  	if len(longestalias) > 0 {
   278  		return e.aliases[longestalias] + rawurl[len(longestalias):]
   279  	}
   280  
   281  	return rawurl
   282  }
   283  
   284  func initAliases(e *endpointGitFinder, git config.Environment) {
   285  	prefix := "url."
   286  	suffix := ".insteadof"
   287  	for gitkey, gitval := range git.All() {
   288  		if len(gitval) == 0 || !(strings.HasPrefix(gitkey, prefix) && strings.HasSuffix(gitkey, suffix)) {
   289  			continue
   290  		}
   291  		if _, ok := e.aliases[gitval[len(gitval)-1]]; ok {
   292  			fmt.Fprintf(os.Stderr, "WARNING: Multiple 'url.*.insteadof' keys with the same alias: %q\n", gitval)
   293  		}
   294  		e.aliases[gitval[len(gitval)-1]] = gitkey[len(prefix) : len(gitkey)-len(suffix)]
   295  	}
   296  }