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 }