2016-12-19 18:45:22 +00:00
|
|
|
package lfsapi
|
|
|
|
|
|
|
|
import (
|
2023-09-08 02:05:39 +00:00
|
|
|
"bufio"
|
2016-12-19 18:45:22 +00:00
|
|
|
"fmt"
|
|
|
|
"net/url"
|
|
|
|
"os"
|
|
|
|
"path"
|
2023-09-08 02:05:39 +00:00
|
|
|
"regexp"
|
2016-12-19 18:45:22 +00:00
|
|
|
"strings"
|
|
|
|
"sync"
|
|
|
|
|
2021-09-01 19:41:10 +00:00
|
|
|
"github.com/git-lfs/git-lfs/v3/config"
|
|
|
|
"github.com/git-lfs/git-lfs/v3/creds"
|
|
|
|
"github.com/git-lfs/git-lfs/v3/git"
|
|
|
|
"github.com/git-lfs/git-lfs/v3/lfshttp"
|
2021-12-14 16:05:42 +00:00
|
|
|
"github.com/git-lfs/git-lfs/v3/tr"
|
2016-12-19 19:24:22 +00:00
|
|
|
"github.com/rubyist/tracerx"
|
2016-12-19 18:45:22 +00:00
|
|
|
)
|
|
|
|
|
2016-12-19 18:55:47 +00:00
|
|
|
const (
|
2019-11-28 19:03:52 +00:00
|
|
|
defaultRemote = "origin"
|
2016-12-19 18:55:47 +00:00
|
|
|
)
|
2016-12-19 18:45:22 +00:00
|
|
|
|
|
|
|
type EndpointFinder interface {
|
2018-10-26 17:41:07 +00:00
|
|
|
NewEndpointFromCloneURL(operation, rawurl string) lfshttp.Endpoint
|
|
|
|
NewEndpoint(operation, rawurl string) lfshttp.Endpoint
|
2018-09-06 21:42:41 +00:00
|
|
|
Endpoint(operation, remote string) lfshttp.Endpoint
|
|
|
|
RemoteEndpoint(operation, remote string) lfshttp.Endpoint
|
2016-12-19 18:45:22 +00:00
|
|
|
GitRemoteURL(remote string, forpush bool) string
|
2019-11-28 19:03:52 +00:00
|
|
|
AccessFor(rawurl string) creds.Access
|
|
|
|
SetAccess(access creds.Access)
|
2016-12-19 18:45:22 +00:00
|
|
|
GitProtocol() string
|
|
|
|
}
|
|
|
|
|
|
|
|
type endpointGitFinder struct {
|
2017-10-25 22:05:08 +00:00
|
|
|
gitConfig *git.Configuration
|
|
|
|
gitEnv config.Environment
|
2016-12-19 18:45:22 +00:00
|
|
|
gitProtocol string
|
2023-10-26 18:53:44 +00:00
|
|
|
gitDir string
|
2016-12-19 19:24:22 +00:00
|
|
|
|
2018-10-26 19:16:07 +00:00
|
|
|
aliasMu sync.Mutex
|
|
|
|
aliases map[string]string
|
|
|
|
pushAliases map[string]string
|
2023-10-26 18:50:14 +00:00
|
|
|
remoteList []string
|
2016-12-19 19:24:22 +00:00
|
|
|
|
|
|
|
accessMu sync.Mutex
|
2019-11-28 19:03:52 +00:00
|
|
|
urlAccess map[string]creds.AccessMode
|
2017-04-17 20:48:36 +00:00
|
|
|
urlConfig *config.URLConfig
|
2016-12-19 18:45:22 +00:00
|
|
|
}
|
|
|
|
|
2018-09-06 21:42:41 +00:00
|
|
|
func NewEndpointFinder(ctx lfshttp.Context) EndpointFinder {
|
2017-10-25 22:05:08 +00:00
|
|
|
if ctx == nil {
|
2018-09-06 21:42:41 +00:00
|
|
|
ctx = lfshttp.NewContext(nil, nil, nil)
|
2017-10-25 22:05:08 +00:00
|
|
|
}
|
|
|
|
|
2023-10-26 18:53:44 +00:00
|
|
|
var gitDir string
|
|
|
|
cfg := ctx.GitConfig()
|
|
|
|
if cfg != nil && cfg.GitDir != "" {
|
|
|
|
gitDir = cfg.GitDir
|
|
|
|
} else if dir, err := git.GitDir(); err == nil {
|
|
|
|
gitDir = dir
|
|
|
|
}
|
|
|
|
|
2016-12-19 18:45:22 +00:00
|
|
|
e := &endpointGitFinder{
|
2017-10-25 22:05:08 +00:00
|
|
|
gitConfig: ctx.GitConfig(),
|
|
|
|
gitEnv: ctx.GitEnv(),
|
2016-12-19 18:45:22 +00:00
|
|
|
gitProtocol: "https",
|
2023-10-26 18:53:44 +00:00
|
|
|
gitDir: gitDir,
|
2016-12-19 18:45:22 +00:00
|
|
|
aliases: make(map[string]string),
|
2018-10-26 19:16:07 +00:00
|
|
|
pushAliases: make(map[string]string),
|
2019-11-28 19:03:52 +00:00
|
|
|
urlAccess: make(map[string]creds.AccessMode),
|
2016-12-19 18:45:22 +00:00
|
|
|
}
|
|
|
|
|
2023-10-26 18:50:14 +00:00
|
|
|
remotes, _ := git.RemoteList()
|
|
|
|
e.remoteList = remotes
|
|
|
|
|
2017-10-25 22:05:08 +00:00
|
|
|
e.urlConfig = config.NewURLConfig(e.gitEnv)
|
|
|
|
if v, ok := e.gitEnv.Get("lfs.gitprotocol"); ok {
|
|
|
|
e.gitProtocol = v
|
2016-12-19 18:45:22 +00:00
|
|
|
}
|
2017-10-25 22:05:08 +00:00
|
|
|
initAliases(e, e.gitEnv)
|
2016-12-19 18:45:22 +00:00
|
|
|
|
|
|
|
return e
|
|
|
|
}
|
|
|
|
|
2018-09-06 21:42:41 +00:00
|
|
|
func (e *endpointGitFinder) Endpoint(operation, remote string) lfshttp.Endpoint {
|
2017-01-25 15:59:31 +00:00
|
|
|
ep := e.getEndpoint(operation, remote)
|
|
|
|
ep.Operation = operation
|
|
|
|
return ep
|
|
|
|
}
|
|
|
|
|
2018-09-06 21:42:41 +00:00
|
|
|
func (e *endpointGitFinder) getEndpoint(operation, remote string) lfshttp.Endpoint {
|
2017-10-25 22:05:08 +00:00
|
|
|
if e.gitEnv == nil {
|
2018-09-06 21:42:41 +00:00
|
|
|
return lfshttp.Endpoint{}
|
2016-12-19 18:45:22 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if operation == "upload" {
|
2017-10-25 22:05:08 +00:00
|
|
|
if url, ok := e.gitEnv.Get("lfs.pushurl"); ok {
|
2018-10-26 17:41:07 +00:00
|
|
|
return e.NewEndpoint(operation, url)
|
2016-12-19 18:45:22 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-10-25 22:05:08 +00:00
|
|
|
if url, ok := e.gitEnv.Get("lfs.url"); ok {
|
2018-10-26 17:41:07 +00:00
|
|
|
return e.NewEndpoint(operation, url)
|
2016-12-19 18:45:22 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if len(remote) > 0 && remote != defaultRemote {
|
|
|
|
if e := e.RemoteEndpoint(operation, remote); len(e.Url) > 0 {
|
|
|
|
return e
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return e.RemoteEndpoint(operation, defaultRemote)
|
|
|
|
}
|
|
|
|
|
2018-09-06 21:42:41 +00:00
|
|
|
func (e *endpointGitFinder) RemoteEndpoint(operation, remote string) lfshttp.Endpoint {
|
2017-10-25 22:05:08 +00:00
|
|
|
if e.gitEnv == nil {
|
2018-09-06 21:42:41 +00:00
|
|
|
return lfshttp.Endpoint{}
|
2016-12-19 18:45:22 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if len(remote) == 0 {
|
|
|
|
remote = defaultRemote
|
|
|
|
}
|
|
|
|
|
|
|
|
// Support separate push URL if specified and pushing
|
|
|
|
if operation == "upload" {
|
2017-10-25 22:05:08 +00:00
|
|
|
if url, ok := e.gitEnv.Get("remote." + remote + ".lfspushurl"); ok {
|
2018-10-26 17:41:07 +00:00
|
|
|
return e.NewEndpoint(operation, url)
|
2016-12-19 18:45:22 +00:00
|
|
|
}
|
|
|
|
}
|
2017-10-25 22:05:08 +00:00
|
|
|
if url, ok := e.gitEnv.Get("remote." + remote + ".lfsurl"); ok {
|
2018-10-26 17:41:07 +00:00
|
|
|
return e.NewEndpoint(operation, url)
|
2016-12-19 18:45:22 +00:00
|
|
|
}
|
|
|
|
|
2023-04-25 07:14:23 +00:00
|
|
|
// fall back on git remote url (also supports pushurl)
|
2016-12-19 18:45:22 +00:00
|
|
|
if url := e.GitRemoteURL(remote, operation == "upload"); url != "" {
|
2018-10-26 17:41:07 +00:00
|
|
|
return e.NewEndpointFromCloneURL(operation, url)
|
2016-12-19 18:45:22 +00:00
|
|
|
}
|
|
|
|
|
2023-09-08 02:05:39 +00:00
|
|
|
// Finally, fall back on .git/FETCH_HEAD but only if it exists and no specific remote was requested
|
|
|
|
// We can't know which remote FETCH_HEAD is pointing to
|
2023-10-26 18:53:44 +00:00
|
|
|
if e.gitDir != "" && remote == defaultRemote {
|
|
|
|
url, err := parseFetchHead(strings.Join([]string{e.gitDir, "FETCH_HEAD"}, "/"))
|
2023-09-08 02:05:39 +00:00
|
|
|
if err == nil {
|
|
|
|
endpoint := e.NewEndpointFromCloneURL("download", url)
|
|
|
|
return endpoint
|
|
|
|
} else {
|
|
|
|
tracerx.Printf("failed parsing FETCH_HEAD: %s", err)
|
|
|
|
}
|
|
|
|
}
|
2023-05-02 08:58:35 +00:00
|
|
|
|
2018-09-06 21:42:41 +00:00
|
|
|
return lfshttp.Endpoint{}
|
2016-12-19 18:45:22 +00:00
|
|
|
}
|
|
|
|
|
2023-04-25 07:14:23 +00:00
|
|
|
func parseFetchHead(filePath string) (string, error) {
|
|
|
|
file, err := os.Open(filePath)
|
|
|
|
if err != nil {
|
|
|
|
return "", err
|
|
|
|
}
|
|
|
|
defer file.Close()
|
|
|
|
|
|
|
|
scanner := bufio.NewScanner(file)
|
|
|
|
if scanner.Scan() {
|
|
|
|
line := scanner.Text()
|
|
|
|
return ExtractRemoteUrl(line)
|
|
|
|
}
|
|
|
|
|
|
|
|
return "", fmt.Errorf("Failed to read content from %s", filePath)
|
|
|
|
}
|
|
|
|
|
|
|
|
func ExtractRemoteUrl(line string) (string, error) {
|
2023-09-08 02:05:39 +00:00
|
|
|
// see https://regex101.com/r/lYla7c/1
|
|
|
|
re := regexp.MustCompile(`^[a-f0-9]{40,64}\t(not-for-merge)?\t(tag |branch |)'.*' of (?P<url>[\/\.\-\:\_a-zA-Z0-9]+)$`)
|
2023-04-25 07:14:23 +00:00
|
|
|
|
|
|
|
match := re.FindStringSubmatch(line)
|
2023-05-16 21:57:35 +00:00
|
|
|
|
2023-09-08 02:05:39 +00:00
|
|
|
for i, name := range re.SubexpNames() {
|
|
|
|
if name == "url" {
|
|
|
|
if len(match) < i {
|
|
|
|
break
|
|
|
|
}
|
|
|
|
return strings.TrimSpace(match[i]), nil
|
|
|
|
}
|
|
|
|
}
|
2023-07-27 07:35:20 +00:00
|
|
|
|
2023-09-08 02:05:39 +00:00
|
|
|
return "", fmt.Errorf("failed to extract remote URL from \"%s\"", line)
|
2023-04-25 07:14:23 +00:00
|
|
|
}
|
|
|
|
|
2016-12-19 18:45:22 +00:00
|
|
|
func (e *endpointGitFinder) GitRemoteURL(remote string, forpush bool) string {
|
2017-10-25 22:05:08 +00:00
|
|
|
if e.gitEnv != nil {
|
2016-12-19 18:45:22 +00:00
|
|
|
if forpush {
|
2017-10-25 22:05:08 +00:00
|
|
|
if u, ok := e.gitEnv.Get("remote." + remote + ".pushurl"); ok {
|
2016-12-19 18:45:22 +00:00
|
|
|
return u
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-10-25 22:05:08 +00:00
|
|
|
if u, ok := e.gitEnv.Get("remote." + remote + ".url"); ok {
|
2016-12-19 18:45:22 +00:00
|
|
|
return u
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-10-26 18:50:14 +00:00
|
|
|
if err := git.ValidateRemoteFromList(e.remoteList, remote); err == nil {
|
2016-12-19 18:45:22 +00:00
|
|
|
return remote
|
|
|
|
}
|
|
|
|
|
|
|
|
return ""
|
|
|
|
}
|
|
|
|
|
2018-10-26 17:41:07 +00:00
|
|
|
func (e *endpointGitFinder) NewEndpointFromCloneURL(operation, rawurl string) lfshttp.Endpoint {
|
|
|
|
ep := e.NewEndpoint(operation, rawurl)
|
2018-09-06 21:42:41 +00:00
|
|
|
if ep.Url == lfshttp.UrlUnknown {
|
2016-12-19 18:45:22 +00:00
|
|
|
return ep
|
|
|
|
}
|
|
|
|
|
lfsapi: gracefully handle local paths with trailing slash
When we want to handle a local path, we must rewrite it internally into
a `file:///` URL, because we dispatch our standalone transfer agent
based on the URL scheme. However, once we've looked up an appropriate
URL and formatted it as the scheme prefers, if it contains a trailing
slash, we ignore the entire lookup and replace the URL with the raw one
given, but without the trailing slash.
This behaviour seems a bit bizarre, and it causes us to take a local
path with a trailing slash and treat it not as the `file:///` URL it
needs to be, but as a raw local path, which gets refused. Let's avoid
this problem by looking at the rewritten URL, and modifying that instead
if we find its trailing slash to be offensive.
2023-06-21 13:09:29 +00:00
|
|
|
if strings.HasSuffix(ep.Url, "/") {
|
|
|
|
ep.Url = ep.Url[0 : len(ep.Url)-1]
|
2016-12-19 18:45:22 +00:00
|
|
|
}
|
|
|
|
|
Add support for local paths
Currently, we only support local remotes using file URLs, but not local
paths. This is a highly requested feature, however, so implement
support for local paths as remotes.
First, fix the handling of local paths we already have: the handling of
absolute Unix-style paths. Instead of checking for the remote to
contain a file URL to determine whether to append "info/lfs" to the URL,
look to see if the URL we're using is a file URL, which will catch local
paths which will have been rewritten as such by this point. The
"info/lfs" part of the URL is not handled by the transfer adapter, so we
don't want to add it on.
If we get something that looks like it's not a URL, check if it's a
file on the file system, and if so, don't attempt to interpret it as an
SSH URL. This fixes the confusion with Windows paths, which resemble
SSH-style locations with a single-letter alias as the host name.
Finally, when turning a local path into a file URL, turn the path into
an absolute one if possible and rewrite it using slashes, which is
required for file URLs. Add several tests for the various cases: one
for Unix-style paths, one for native paths, and one for relative paths.
2019-11-13 21:16:45 +00:00
|
|
|
if strings.HasPrefix(ep.Url, "file://") {
|
2019-07-31 13:46:39 +00:00
|
|
|
return ep
|
|
|
|
}
|
|
|
|
|
2016-12-19 18:45:22 +00:00
|
|
|
// 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
|
|
|
|
}
|
|
|
|
|
2018-10-26 17:41:07 +00:00
|
|
|
func (e *endpointGitFinder) NewEndpoint(operation, rawurl string) lfshttp.Endpoint {
|
2018-10-26 19:16:07 +00:00
|
|
|
rawurl = e.ReplaceUrlAlias(operation, rawurl)
|
2018-03-10 14:43:44 +00:00
|
|
|
if strings.HasPrefix(rawurl, "/") {
|
2018-09-06 21:42:41 +00:00
|
|
|
return lfshttp.EndpointFromLocalPath(rawurl)
|
2018-03-10 14:43:44 +00:00
|
|
|
}
|
2016-12-19 18:45:22 +00:00
|
|
|
u, err := url.Parse(rawurl)
|
|
|
|
if err != nil {
|
2018-09-06 21:42:41 +00:00
|
|
|
return lfshttp.EndpointFromBareSshUrl(rawurl)
|
2016-12-19 18:45:22 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
switch u.Scheme {
|
2019-07-15 21:05:09 +00:00
|
|
|
case "ssh", "git+ssh", "ssh+git":
|
2018-09-06 21:42:41 +00:00
|
|
|
return lfshttp.EndpointFromSshUrl(u)
|
2016-12-19 18:45:22 +00:00
|
|
|
case "http", "https":
|
2018-09-06 21:42:41 +00:00
|
|
|
return lfshttp.EndpointFromHttpUrl(u)
|
2016-12-19 18:45:22 +00:00
|
|
|
case "git":
|
|
|
|
return endpointFromGitUrl(u, e)
|
2019-07-30 17:52:56 +00:00
|
|
|
case "file":
|
|
|
|
return lfshttp.EndpointFromFileUrl(u)
|
2016-12-19 18:45:22 +00:00
|
|
|
case "":
|
Add support for local paths
Currently, we only support local remotes using file URLs, but not local
paths. This is a highly requested feature, however, so implement
support for local paths as remotes.
First, fix the handling of local paths we already have: the handling of
absolute Unix-style paths. Instead of checking for the remote to
contain a file URL to determine whether to append "info/lfs" to the URL,
look to see if the URL we're using is a file URL, which will catch local
paths which will have been rewritten as such by this point. The
"info/lfs" part of the URL is not handled by the transfer adapter, so we
don't want to add it on.
If we get something that looks like it's not a URL, check if it's a
file on the file system, and if so, don't attempt to interpret it as an
SSH URL. This fixes the confusion with Windows paths, which resemble
SSH-style locations with a single-letter alias as the host name.
Finally, when turning a local path into a file URL, turn the path into
an absolute one if possible and rewrite it using slashes, which is
required for file URLs. Add several tests for the various cases: one
for Unix-style paths, one for native paths, and one for relative paths.
2019-11-13 21:16:45 +00:00
|
|
|
// If it looks like a local path, it probably is.
|
|
|
|
if _, err := os.Stat(rawurl); err == nil {
|
|
|
|
return lfshttp.EndpointFromLocalPath(rawurl)
|
|
|
|
}
|
2018-09-06 21:42:41 +00:00
|
|
|
return lfshttp.EndpointFromBareSshUrl(u.String())
|
2016-12-19 18:45:22 +00:00
|
|
|
default:
|
lfsapi: handle SSH hostnames and aliases without users
It's very common for a user to specify an SSH remote that refers to an
alias in their gitconfig file. For example, if a user must use
different SSH keys when pushing to different remotes, setting different
aliases that use those different keys is the simplest and easiest way to
accomplish that goal.
Unfortunately, our parsing logic was broken for SSH remotes that used
bare SSH URLs (that is, without “ssh://”) with a hostname or alias, but
without a username component. The URL parser we used saw the hostname
portion as the scheme of a URL, and we passed through any URLs with
unknown schemes. This led to a failure to push, since we misparsed the
URL as a non-SSH URL and gave up when it also didn't look like an
HTTP(S) URL.
If we get an unknown “scheme” when parsing a URL, check if the URL uses
a remote helper (that is, it contains a double colon), and pass it
through. Otherwise, do what Git does, and assume the URL is in fact an
SSH URL, and handle it accordingly.
We already handle the url.insteadof syntax properly, even if it looks
like a scheme. Add tests that we continue to do so, and for the new
code and all the various permutations mentioned above, and update the
existing integration test to reflect our new parsing strategy.
2018-09-05 21:23:12 +00:00
|
|
|
if strings.HasPrefix(rawurl, u.Scheme+"::") {
|
|
|
|
// Looks like a remote helper; just pass it through.
|
2018-09-06 21:42:41 +00:00
|
|
|
return lfshttp.Endpoint{Url: rawurl}
|
lfsapi: handle SSH hostnames and aliases without users
It's very common for a user to specify an SSH remote that refers to an
alias in their gitconfig file. For example, if a user must use
different SSH keys when pushing to different remotes, setting different
aliases that use those different keys is the simplest and easiest way to
accomplish that goal.
Unfortunately, our parsing logic was broken for SSH remotes that used
bare SSH URLs (that is, without “ssh://”) with a hostname or alias, but
without a username component. The URL parser we used saw the hostname
portion as the scheme of a URL, and we passed through any URLs with
unknown schemes. This led to a failure to push, since we misparsed the
URL as a non-SSH URL and gave up when it also didn't look like an
HTTP(S) URL.
If we get an unknown “scheme” when parsing a URL, check if the URL uses
a remote helper (that is, it contains a double colon), and pass it
through. Otherwise, do what Git does, and assume the URL is in fact an
SSH URL, and handle it accordingly.
We already handle the url.insteadof syntax properly, even if it looks
like a scheme. Add tests that we continue to do so, and for the new
code and all the various permutations mentioned above, and update the
existing integration test to reflect our new parsing strategy.
2018-09-05 21:23:12 +00:00
|
|
|
}
|
Add support for local paths
Currently, we only support local remotes using file URLs, but not local
paths. This is a highly requested feature, however, so implement
support for local paths as remotes.
First, fix the handling of local paths we already have: the handling of
absolute Unix-style paths. Instead of checking for the remote to
contain a file URL to determine whether to append "info/lfs" to the URL,
look to see if the URL we're using is a file URL, which will catch local
paths which will have been rewritten as such by this point. The
"info/lfs" part of the URL is not handled by the transfer adapter, so we
don't want to add it on.
If we get something that looks like it's not a URL, check if it's a
file on the file system, and if so, don't attempt to interpret it as an
SSH URL. This fixes the confusion with Windows paths, which resemble
SSH-style locations with a single-letter alias as the host name.
Finally, when turning a local path into a file URL, turn the path into
an absolute one if possible and rewrite it using slashes, which is
required for file URLs. Add several tests for the various cases: one
for Unix-style paths, one for native paths, and one for relative paths.
2019-11-13 21:16:45 +00:00
|
|
|
// If it looks like a local path, it probably is.
|
|
|
|
if _, err := os.Stat(rawurl); err == nil {
|
|
|
|
return lfshttp.EndpointFromLocalPath(rawurl)
|
|
|
|
}
|
lfsapi: handle SSH hostnames and aliases without users
It's very common for a user to specify an SSH remote that refers to an
alias in their gitconfig file. For example, if a user must use
different SSH keys when pushing to different remotes, setting different
aliases that use those different keys is the simplest and easiest way to
accomplish that goal.
Unfortunately, our parsing logic was broken for SSH remotes that used
bare SSH URLs (that is, without “ssh://”) with a hostname or alias, but
without a username component. The URL parser we used saw the hostname
portion as the scheme of a URL, and we passed through any URLs with
unknown schemes. This led to a failure to push, since we misparsed the
URL as a non-SSH URL and gave up when it also didn't look like an
HTTP(S) URL.
If we get an unknown “scheme” when parsing a URL, check if the URL uses
a remote helper (that is, it contains a double colon), and pass it
through. Otherwise, do what Git does, and assume the URL is in fact an
SSH URL, and handle it accordingly.
We already handle the url.insteadof syntax properly, even if it looks
like a scheme. Add tests that we continue to do so, and for the new
code and all the various permutations mentioned above, and update the
existing integration test to reflect our new parsing strategy.
2018-09-05 21:23:12 +00:00
|
|
|
// We probably got here because the "scheme" that was parsed is
|
|
|
|
// 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
|
|
|
|
// it as an SSH URL. This ensures we handle SSH config aliases
|
|
|
|
// properly.
|
2018-09-06 21:42:41 +00:00
|
|
|
return lfshttp.EndpointFromBareSshUrl(u.String())
|
2016-12-19 18:45:22 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-11-28 19:03:52 +00:00
|
|
|
func (e *endpointGitFinder) AccessFor(rawurl string) creds.Access {
|
2018-09-24 18:50:09 +00:00
|
|
|
accessurl := urlWithoutAuth(rawurl)
|
|
|
|
|
2017-10-25 22:05:08 +00:00
|
|
|
if e.gitEnv == nil {
|
2019-11-28 19:03:52 +00:00
|
|
|
return creds.NewAccess(creds.NoneAccess, accessurl)
|
2016-12-19 18:55:47 +00:00
|
|
|
}
|
|
|
|
|
2016-12-19 19:24:22 +00:00
|
|
|
e.accessMu.Lock()
|
|
|
|
defer e.accessMu.Unlock()
|
|
|
|
|
2016-12-23 17:42:57 +00:00
|
|
|
if cached, ok := e.urlAccess[accessurl]; ok {
|
2019-11-28 19:03:52 +00:00
|
|
|
return creds.NewAccess(cached, accessurl)
|
2016-12-19 19:24:22 +00:00
|
|
|
}
|
|
|
|
|
2017-04-17 20:49:32 +00:00
|
|
|
e.urlAccess[accessurl] = e.fetchGitAccess(accessurl)
|
2019-11-28 19:03:52 +00:00
|
|
|
return creds.NewAccess(e.urlAccess[accessurl], accessurl)
|
2016-12-19 19:24:22 +00:00
|
|
|
}
|
|
|
|
|
2019-11-28 19:03:52 +00:00
|
|
|
func (e *endpointGitFinder) SetAccess(access creds.Access) {
|
|
|
|
key := fmt.Sprintf("lfs.%s.access", access.URL())
|
2018-10-02 23:35:54 +00:00
|
|
|
tracerx.Printf("setting repository access to %s", access.Mode())
|
2016-12-19 19:24:22 +00:00
|
|
|
|
|
|
|
e.accessMu.Lock()
|
|
|
|
defer e.accessMu.Unlock()
|
|
|
|
|
2018-10-02 23:35:54 +00:00
|
|
|
switch access.Mode() {
|
2019-11-28 19:03:52 +00:00
|
|
|
case creds.EmptyAccess, creds.NoneAccess:
|
2017-10-26 01:20:35 +00:00
|
|
|
e.gitConfig.UnsetLocalKey(key)
|
2019-11-28 19:03:52 +00:00
|
|
|
e.urlAccess[access.URL()] = creds.NoneAccess
|
2016-12-19 19:24:22 +00:00
|
|
|
default:
|
2018-10-02 23:35:54 +00:00
|
|
|
e.gitConfig.SetLocal(key, string(access.Mode()))
|
2019-11-28 19:03:52 +00:00
|
|
|
e.urlAccess[access.URL()] = access.Mode()
|
2016-12-19 19:24:22 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-12-23 17:42:57 +00:00
|
|
|
func urlWithoutAuth(rawurl string) string {
|
|
|
|
if !strings.Contains(rawurl, "@") {
|
|
|
|
return rawurl
|
|
|
|
}
|
|
|
|
|
|
|
|
u, err := url.Parse(rawurl)
|
|
|
|
if err != nil {
|
|
|
|
fmt.Fprintf(os.Stderr, "Error parsing URL %q: %s", rawurl, err)
|
|
|
|
return rawurl
|
|
|
|
}
|
|
|
|
|
|
|
|
u.User = nil
|
|
|
|
return u.String()
|
|
|
|
}
|
|
|
|
|
2019-11-28 19:03:52 +00:00
|
|
|
func (e *endpointGitFinder) fetchGitAccess(rawurl string) creds.AccessMode {
|
2017-04-17 20:49:32 +00:00
|
|
|
if v, _ := e.urlConfig.Get("lfs", rawurl, "access"); len(v) > 0 {
|
2019-11-28 19:03:52 +00:00
|
|
|
access := creds.AccessMode(strings.ToLower(v))
|
|
|
|
if access == creds.PrivateAccess {
|
|
|
|
return creds.BasicAccess
|
2016-12-19 18:55:47 +00:00
|
|
|
}
|
2016-12-19 19:24:22 +00:00
|
|
|
return access
|
2016-12-19 18:55:47 +00:00
|
|
|
}
|
2019-11-28 19:03:52 +00:00
|
|
|
return creds.NoneAccess
|
2016-12-19 18:55:47 +00:00
|
|
|
}
|
|
|
|
|
2016-12-19 18:45:22 +00:00
|
|
|
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.
|
2018-10-26 19:16:07 +00:00
|
|
|
func (e *endpointGitFinder) ReplaceUrlAlias(operation, rawurl string) string {
|
2016-12-19 18:45:22 +00:00
|
|
|
e.aliasMu.Lock()
|
|
|
|
defer e.aliasMu.Unlock()
|
|
|
|
|
2018-10-26 19:16:07 +00:00
|
|
|
if operation == "upload" {
|
|
|
|
if rawurl, replaced := e.replaceUrlAlias(e.pushAliases, rawurl); replaced {
|
|
|
|
return rawurl
|
|
|
|
}
|
|
|
|
}
|
2018-10-26 19:14:06 +00:00
|
|
|
rawurl, _ = e.replaceUrlAlias(e.aliases, rawurl)
|
|
|
|
|
|
|
|
return rawurl
|
|
|
|
}
|
|
|
|
|
|
|
|
// replaceUrlAlias is a helper function for ReplaceUrlAlias. It must only be
|
|
|
|
// called while the e.aliasMu mutex is held.
|
|
|
|
func (e *endpointGitFinder) replaceUrlAlias(aliases map[string]string, rawurl string) (string, bool) {
|
2016-12-19 18:45:22 +00:00
|
|
|
var longestalias string
|
2018-10-26 19:14:06 +00:00
|
|
|
for alias, _ := range aliases {
|
2016-12-19 18:45:22 +00:00
|
|
|
if !strings.HasPrefix(rawurl, alias) {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
if longestalias < alias {
|
|
|
|
longestalias = alias
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if len(longestalias) > 0 {
|
2018-10-26 19:14:06 +00:00
|
|
|
return aliases[longestalias] + rawurl[len(longestalias):], true
|
2016-12-19 18:45:22 +00:00
|
|
|
}
|
|
|
|
|
2018-10-26 19:14:06 +00:00
|
|
|
return rawurl, false
|
2016-12-19 18:45:22 +00:00
|
|
|
}
|
|
|
|
|
2018-10-26 18:08:06 +00:00
|
|
|
const (
|
|
|
|
aliasPrefix = "url."
|
|
|
|
)
|
|
|
|
|
2017-10-25 21:33:20 +00:00
|
|
|
func initAliases(e *endpointGitFinder, git config.Environment) {
|
2016-12-19 18:45:22 +00:00
|
|
|
suffix := ".insteadof"
|
2018-10-26 19:16:07 +00:00
|
|
|
pushSuffix := ".pushinsteadof"
|
2016-12-19 18:45:22 +00:00
|
|
|
for gitkey, gitval := range git.All() {
|
2018-10-26 19:16:07 +00:00
|
|
|
if len(gitval) == 0 || !strings.HasPrefix(gitkey, aliasPrefix) {
|
2016-12-19 18:45:22 +00:00
|
|
|
continue
|
|
|
|
}
|
2018-10-26 19:16:07 +00:00
|
|
|
if strings.HasSuffix(gitkey, suffix) {
|
|
|
|
storeAlias(e.aliases, gitkey, gitval, suffix)
|
|
|
|
} else if strings.HasSuffix(gitkey, pushSuffix) {
|
|
|
|
storeAlias(e.pushAliases, gitkey, gitval, pushSuffix)
|
|
|
|
}
|
2018-10-26 18:08:06 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-03-01 19:31:45 +00:00
|
|
|
func storeAlias(aliases map[string]string, key string, values []string, suffix string) {
|
|
|
|
for _, value := range values {
|
lfsapi: don't warn about duplicate but identical aliases
Currently, we warn about aliases (e.g., url.*.insteadOf) when there's
already an alias for the same URL; that is, when the insteadOf entry
matches an existing one. However, in some environments, multiple
aliases are defined, but all to the same root URL (the one in the key).
In such a case, don't warn, since these aliases are already identical.
Do continue to warn if the root URL is different at all, since these are
different aliases for the same URL, and add a test for both of these
cases.
2021-02-24 17:44:27 +00:00
|
|
|
url := key[len(aliasPrefix) : len(key)-len(suffix)]
|
|
|
|
if v, ok := aliases[value]; ok && v != url {
|
commands,lfs*: remove most newlines from messages
In general, we can make the translation task somewhat
easier for translators, and less prone to error, if we
remove non-language-specific newlines from the text strings
to be translated.
To do this we can in some cases simply drop newlines (e.g.,
in panic messages), or break multi-line text strings into
several separate messages where that makes sense. For the
most part we simply append the necessary trailing or
intermediate newlines using the formatting tools available,
especially those provided in the commands/commands.go file.
Note that those tools (i.e., Print(), Exit(), etc.) always
append a newline, but some messages are intended to have
two final newlines, such as those from the "git lfs status"
command.
We also add a note to translators regarding some specific
spacing required by a message output by the "git lfs dedup"
command.
2022-01-27 06:39:31 +00:00
|
|
|
fmt.Fprintln(os.Stderr, tr.Tr.Get("warning: Multiple 'url.*.%s' keys with the same alias: %q", suffix, value))
|
2019-03-01 19:31:45 +00:00
|
|
|
}
|
lfsapi: don't warn about duplicate but identical aliases
Currently, we warn about aliases (e.g., url.*.insteadOf) when there's
already an alias for the same URL; that is, when the insteadOf entry
matches an existing one. However, in some environments, multiple
aliases are defined, but all to the same root URL (the one in the key).
In such a case, don't warn, since these aliases are already identical.
Do continue to warn if the root URL is different at all, since these are
different aliases for the same URL, and add a test for both of these
cases.
2021-02-24 17:44:27 +00:00
|
|
|
aliases[value] = url
|
2016-12-19 18:45:22 +00:00
|
|
|
}
|
|
|
|
}
|
2018-09-06 21:42:41 +00:00
|
|
|
|
|
|
|
func endpointFromGitUrl(u *url.URL, e *endpointGitFinder) lfshttp.Endpoint {
|
|
|
|
u.Scheme = e.gitProtocol
|
|
|
|
return lfshttp.Endpoint{Url: u.String()}
|
|
|
|
}
|