package lfsapi import ( "fmt" "net/url" "os" "path" "strings" "sync" "github.com/git-lfs/git-lfs/git" ) const defaultRemote = "origin" type EndpointFinder interface { NewEndpointFromCloneURL(rawurl string) Endpoint NewEndpoint(rawurl string) Endpoint Endpoint(operation, remote string) Endpoint RemoteEndpoint(operation, remote string) Endpoint GitRemoteURL(remote string, forpush bool) string AccessFor(ep Endpoint) Access GitProtocol() string } type endpointGitFinder struct { git env gitProtocol string aliases map[string]string aliasMu sync.Mutex } func NewEndpointFinder(git env) EndpointFinder { e := &endpointGitFinder{ gitProtocol: "https", aliases: make(map[string]string), } if git != nil { e.git = git if v, ok := git.Get("lfs.gitprotocol"); ok { e.gitProtocol = v } initAliases(e, git) } return e } func (e *endpointGitFinder) Endpoint(operation, remote string) Endpoint { if e.git == nil { return Endpoint{} } if operation == "upload" { if url, ok := e.git.Get("lfs.pushurl"); ok { return e.NewEndpoint(url) } } if url, ok := e.git.Get("lfs.url"); ok { return e.NewEndpoint(url) } if len(remote) > 0 && remote != defaultRemote { if e := e.RemoteEndpoint(operation, remote); len(e.Url) > 0 { return e } } return e.RemoteEndpoint(operation, defaultRemote) } func (e *endpointGitFinder) RemoteEndpoint(operation, remote string) Endpoint { if e.git == nil { return Endpoint{} } if len(remote) == 0 { remote = defaultRemote } // Support separate push URL if specified and pushing if operation == "upload" { if url, ok := e.git.Get("remote." + remote + ".lfspushurl"); ok { return e.NewEndpoint(url) } } if url, ok := e.git.Get("remote." + remote + ".lfsurl"); ok { return e.NewEndpoint(url) } // finally fall back on git remote url (also supports pushurl) if url := e.GitRemoteURL(remote, operation == "upload"); url != "" { return e.NewEndpointFromCloneURL(url) } return Endpoint{} } func (e *endpointGitFinder) GitRemoteURL(remote string, forpush bool) string { if e.git != nil { if forpush { if u, ok := e.git.Get("remote." + remote + ".pushurl"); ok { return u } } if u, ok := e.git.Get("remote." + remote + ".url"); ok { return u } } if err := git.ValidateRemote(remote); err == nil { return remote } return "" } func (e *endpointGitFinder) NewEndpointFromCloneURL(rawurl string) Endpoint { ep := e.NewEndpoint(rawurl) if ep.Url == UrlUnknown { return ep } if strings.HasSuffix(rawurl, "/") { ep.Url = rawurl[0 : len(rawurl)-1] } // When using main remote URL for HTTP, append info/lfs if path.Ext(ep.Url) == ".git" { ep.Url += "/info/lfs" } else { ep.Url += ".git/info/lfs" } return ep } func (e *endpointGitFinder) NewEndpoint(rawurl string) Endpoint { rawurl = e.ReplaceUrlAlias(rawurl) u, err := url.Parse(rawurl) if err != nil { return endpointFromBareSshUrl(rawurl) } switch u.Scheme { case "ssh": return endpointFromSshUrl(u) case "http", "https": return endpointFromHttpUrl(u) case "git": return endpointFromGitUrl(u, e) case "": return endpointFromBareSshUrl(u.String()) default: // Just passthrough to preserve return Endpoint{Url: rawurl} } } func (e *endpointGitFinder) GitProtocol() string { return e.gitProtocol } // ReplaceUrlAlias returns a url with a prefix from a `url.*.insteadof` git // config setting. If multiple aliases match, use the longest one. // See https://git-scm.com/docs/git-config for Git's docs. func (e *endpointGitFinder) ReplaceUrlAlias(rawurl string) string { e.aliasMu.Lock() defer e.aliasMu.Unlock() var longestalias string for alias, _ := range e.aliases { if !strings.HasPrefix(rawurl, alias) { continue } if longestalias < alias { longestalias = alias } } if len(longestalias) > 0 { return e.aliases[longestalias] + rawurl[len(longestalias):] } return rawurl } func initAliases(e *endpointGitFinder, git env) { prefix := "url." suffix := ".insteadof" for gitkey, gitval := range git.All() { if !(strings.HasPrefix(gitkey, prefix) && strings.HasSuffix(gitkey, suffix)) { continue } if _, ok := e.aliases[gitval]; ok { fmt.Fprintf(os.Stderr, "WARNING: Multiple 'url.*.insteadof' keys with the same alias: %q\n", gitval) } e.aliases[gitval] = gitkey[len(prefix) : len(gitkey)-len(suffix)] } } type env interface { Get(string) (string, bool) All() map[string]string }