Copyright 2019 The Go Authors. All rights reserved. Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
Package source constructs public URLs that link to the source files in a module. It can be used to build references to Go source code, or to any other files in a module. Of course, the module zip file contains all the files in the module. This package attempts to find the origin of the zip file, in a repository that is publicly readable, and constructs links to that repo. While a module zip file could in theory come from anywhere, including a non-public location, this package recognizes standard module path patterns and construct repository URLs from them, like the go command does.
package source
Much of this code was adapted from https://go.googlesource.com/gddo/+/refs/heads/master/gosrc and https://go.googlesource.com/go/+/refs/heads/master/src/cmd/go/internal/get

import (
	
	
	
	
	
	
	
	
	

	
	
	
	
	
	
	
)
Info holds source information about a module, used to generate URLs referring to directories, files and lines.
type Info struct {
	repoURL   string       // URL of repo containing module; exported for DB schema compatibility
	moduleDir string       // directory of module relative to repo root
	commit    string       // tag or ID of commit corresponding to version
	templates urlTemplates // for building URLs
}
RepoURL returns a URL for the home page of the repository.
func ( *Info) () string {
	if  == nil {
		return ""
	}
The default repo template is just "{repo}".
		return .repoURL
	}
	return expand(.templates.Repo, map[string]string{
		"repo": .repoURL,
	})
}
ModuleURL returns a URL for the home page of the module.
func ( *Info) () string {
	return .DirectoryURL("")
}
DirectoryURL returns a URL for a directory relative to the module's home directory.
func ( *Info) ( string) string {
	if  == nil {
		return ""
	}
	return strings.TrimSuffix(expand(.templates.Directory, map[string]string{
		"repo":       .repoURL,
		"importPath": path.Join(strings.TrimPrefix(.repoURL, "https://"), ),
		"commit":     .commit,
		"dir":        path.Join(.moduleDir, ),
	}), "/")
}
FileURL returns a URL for a file whose pathname is relative to the module's home directory.
func ( *Info) ( string) string {
	if  == nil {
		return ""
	}
	,  := path.Split()
	return expand(.templates.File, map[string]string{
		"repo":       .repoURL,
		"importPath": path.Join(strings.TrimPrefix(.repoURL, "https://"), ),
		"commit":     .commit,
		"dir":        ,
		"file":       path.Join(.moduleDir, ),
		"base":       ,
	})
}
LineURL returns a URL referring to a line in a file relative to the module's home directory.
func ( *Info) ( string,  int) string {
	if  == nil {
		return ""
	}
	,  := path.Split()
	return expand(.templates.Line, map[string]string{
		"repo":       .repoURL,
		"importPath": path.Join(strings.TrimPrefix(.repoURL, "https://"), ),
		"commit":     .commit,
		"file":       path.Join(.moduleDir, ),
		"dir":        ,
		"base":       ,
		"line":       strconv.Itoa(),
	})
}
RawURL returns a URL referring to the raw contents of a file relative to the module's home directory.
func ( *Info) ( string) string {
	if  == nil {
		return ""
Some templates don't support raw content serving.
	if .templates.Raw == "" {
		return ""
	}
Special case: the standard library's source module path is set to "src", which is correct for source file links. But the README is at the repo root, not in the src directory. In other words, Module.Units[0].Readme.FilePath is not relative to Module.Units[0].SourceInfo.moduleDir, as it is for every other module. Correct for that here.
	if .repoURL == stdlib.GoSourceRepoURL {
		 = ""
	}
	return expand(.templates.Raw, map[string]string{
		"repo":   .repoURL,
		"commit": .commit,
		"file":   path.Join(, ),
	})
}
map of common urlTemplates
var urlTemplatesByKind = map[string]urlTemplates{
	"github":    githubURLTemplates,
	"gitlab":    githubURLTemplates, // preserved for backwards compatibility (DB still has source_info->Kind = "gitlab")
	"bitbucket": bitbucketURLTemplates,
}
jsonInfo is a Go struct describing the JSON structure of an INFO.
Store common templates efficiently by setting this to a short string we look up in a map. If Kind != "", then Templates == nil.
	Kind      string        `json:",omitempty"`
	Templates *urlTemplates `json:",omitempty"`
}
ToJSONForDB returns the Info encoded for storage in the database.
func ( *Info) () ( []byte,  error) {
	defer derrors.Wrap(&, "MarshalJSON")

	 := &jsonInfo{
		RepoURL:   .repoURL,
		ModuleDir: .moduleDir,
		Commit:    .commit,
Store common templates efficiently, by name.
	for ,  := range urlTemplatesByKind {
		if .templates ==  {
			.Kind = 
			break
		}
We used to use different templates for GitHub and GitLab. Now that they're the same, prefer "github" for consistency (map random iteration order means we could get either here).
	if .Kind == "gitlab" {
		.Kind = "github"
	}
	if .Kind == "" && .templates != (urlTemplates{}) {
		.Templates = &.templates
	}
	return json.Marshal()
}

func ( *Info) ( []byte) ( error) {
	defer derrors.Wrap(&, "UnmarshalJSON(data)")

	var  jsonInfo
	if  := json.Unmarshal(, &);  != nil {
		return 
	}
	.repoURL = trimVCSSuffix(.RepoURL)
	.moduleDir = .ModuleDir
	.commit = .Commit
	if .Kind != "" {
		.templates = urlTemplatesByKind[.Kind]
	} else if .Templates != nil {
		.templates = *.Templates
	}
	return nil
}

client used for HTTP requests. It is mutable for testing purposes. If nil, then moduleInfoDynamic will return nil, nil; also for testing.
New constructs a *Client using the provided timeout.
func ( time.Duration) *Client {
	return &Client{
		httpClient: &http.Client{
			Transport: &ochttp.Transport{},
			Timeout:   ,
		},
	}
}
NewClientForTesting returns a Client suitable for testing. It returns the same results as an ordinary client for statically recognizable paths, but always returns a nil *Info for dynamic paths (those requiring HTTP requests).
func () *Client {
	return &Client{}
}
doURL makes an HTTP request using the given url and method. It returns an error if the request returns an error. If only200 is true, it also returns an error if any status code other than 200 is returned.
func ( *Client) ( context.Context, ,  string,  bool) ( *http.Response,  error) {
	defer derrors.Wrap(&, "doURL(ctx, client, %q, %q)", , )

	if  == nil || .httpClient == nil {
		return nil, fmt.Errorf("c.httpClient cannot be nil")
	}
	,  := http.NewRequest(, , nil)
	if  != nil {
		return nil, 
	}
	,  := ctxhttp.Do(, .httpClient, )
	if  != nil {
		return nil, 
	}
	if  && .StatusCode != 200 {
		.Body.Close()
		return nil, fmt.Errorf("status %s", .Status)
	}
	return , nil
}
ModuleInfo determines the repository corresponding to the module path. It returns a URL to that repo, as well as the directory of the module relative to the repo root. ModuleInfo may fetch from arbitrary URLs, so it can be slow.
func ( context.Context,  *Client, ,  string) ( *Info,  error) {
	defer derrors.Wrap(&, "source.ModuleInfo(ctx, %q, %q)", , )
	,  := trace.StartSpan(, "source.ModuleInfo")
	defer .End()
The example.com domain can never be real; it is reserved for testing (https://en.wikipedia.org/wiki/Example.com). Treat it as if it used GitHub templates.
	if strings.HasPrefix(, "example.com/") {
		return NewGitHubInfo("https://"+, "", ), nil
	}

	if  == stdlib.ModulePath {
		return newStdlibInfo()
	}
	, , , ,  := matchStatic()
	if  != nil {
		,  = moduleInfoDynamic(, , , )
		if  != nil {
			return nil, 
		}
	} else {
		,  := commitFromVersion(, )
		if  != nil {
			 = (, )
		}
		 = &Info{
			repoURL:   trimVCSSuffix("https://" + ),
			moduleDir: ,
			commit:    ,
			templates: ,
		}
	}
	if  != nil {
		adjustVersionedModuleDirectory(, , )
	}
TODO(golang/go#39627): support launchpad.net, including the special case in cmd/go/internal/get/vcs.go.
}

func ( string) ( *Info,  error) {
	defer derrors.Wrap(&, "newStdlibInfo(%q)", )

	,  := stdlib.TagForVersion()
	if  != nil {
		return nil, 
	}

	 := csopensourceTemplates
	.Raw = "https://github.com/golang/go/raw/{commit}/{file}"
	return &Info{
		repoURL:   stdlib.GoSourceRepoURL,
		moduleDir: stdlib.Directory(),
		commit:    ,
		templates: ,
	}, nil
}
matchStatic matches the given module or repo path against a list of known patterns. It returns the repo name, the module path relative to the repo root, and URL templates if there is a match. The relative module path may not be correct in all cases: it is wrong if it ends in a version that is not part of the repo directory structure, because the repo follows the "major branch" convention for versions 2 and above. E.g. this function could return "foo/v2", but the module files live under "foo"; the "/v2" is part of the module path (and the import paths of its packages) but is not a subdirectory. This mistake is corrected in adjustVersionedModuleDirectory, once we have all the information we need to fix it. repo + "/" + relativeModulePath is often, but not always, equal to moduleOrRepoPath. It is not when the argument is a module path that uses the go command's general syntax, which ends in a ".vcs" (e.g. ".git", ".hg") that is neither part of the repo nor the suffix. For example, if the argument is github.com/a/b/c then repo="github.com/a/b" and relativeModulePath="c"; together they make up the module path. But if the argument is example.com/a/b.git/c then repo="example.com/a/b" and relativeModulePath="c"; the ".git" is omitted, since it is neither part of the repo nor part of the relative path to the module within the repo.
func ( string) (,  string,  urlTemplates,  transformCommitFunc,  error) {
	for ,  := range patterns {
		 := .re.FindStringSubmatch()
		if  == nil {
			continue
		}
		var  string
		for ,  := range .re.SubexpNames() {
			if  == "repo" {
				 = []
				break
			}
Special case: git.apache.org has a go-import tag that points to github.com/apache, but it's not quite right (the repo prefix is missing a ".git"), so handle it here.
		const  = "git.apache.org/"
		if strings.HasPrefix(, ) {
			 = strings.Replace(, , "github.com/apache/", 1)
Special case: module paths are blitiri.com.ar/go/..., but repos are blitiri.com.ar/git/r/...
		if strings.HasPrefix(, "blitiri.com.ar/") {
			 = strings.Replace(, "/go/", "/git/r/", 1)
		}
		 = strings.TrimPrefix(, [0])
		 = strings.TrimPrefix(, "/")
		return , , .templates, .transformCommit, nil
	}
	return "", "", urlTemplates{}, nil, derrors.NotFound
}
moduleInfoDynamic uses the go-import and go-source meta tags to construct an Info.
func ( context.Context,  *Client, ,  string) ( *Info,  error) {
	defer derrors.Wrap(&, "source.moduleInfoDynamic(ctx, client, %q, %q)", , )

	if .httpClient == nil {
		return nil, nil // for testing
	}

	,  := fetchMeta(, , )
	if  != nil {
		return nil, 
Don't check that the tag information at the repo root prefix is the same as in the module path. It was done for us by the proxy and/or go command. (This lets us merge information from the go-import and go-source tags.)
sourceMeta contains some information about where the module's source lives. But there are some problems: - We may only have a go-import tag, not a go-source tag, so we don't have URL templates for building URLs to files and directories. - Even if we do have a go-source tag, its URL template format predates versioning, so the URL templates won't provide a way to specify a version or commit. We resolve these problems as follows: 1. First look at the repo URL from the tag. If that matches a known hosting site, use the URL templates corresponding to that site and ignore whatever's in the tag. 2. Then look at the URL templates to see if they match a known pattern, and use the templates from that pattern. For example, the meta tags for gopkg.in/yaml.v2 only mention github in the URL templates, like "https://github.com/go-yaml/yaml/tree/v2.2.3{/dir}". We can observe that that template begins with a known pattern--a GitHub repo, ignore the rest of it, and use the GitHub URL templates that we know.
	 := .repoURL
If err != nil, templates will be the zero value, so we can ignore it (same just below).
	if  == (urlTemplates{}) {
		var  string
		, _, , , _ = matchStatic(removeHTTPScheme(.dirTemplate))
		if  == (urlTemplates{}) {
			if  == nil {
				,  = matchLegacyTemplates(, )
				 = strings.TrimSuffix(, ".git")
			} else {
				log.Infof(, "no templates for repo URL %q from meta tag: err=%v", .repoURL, )
			}
Use the repo from the template, not the original one.
			 = "https://" + 
		}
	}
	 := strings.TrimPrefix(strings.TrimPrefix(, .repoRootPrefix), "/")
	,  := commitFromVersion(, )
	if  != nil {
		 = (, )
	}
	return &Info{
		repoURL:   strings.TrimSuffix(, "/"),
		moduleDir: ,
		commit:    ,
		templates: ,
	}, nil
}
List of template regexps and their corresponding likely templates, used by matchLegacyTemplates below.
var legacyTemplateMatches = []struct {
	fileRegexp      *regexp.Regexp
	templates       urlTemplates
	transformCommit transformCommitFunc
}{
	{
		regexp.MustCompile(`/src/branch/\w+\{/dir\}/\{file\}#L\{line\}$`),
		giteaURLTemplates, giteaTransformCommit,
	},
	{
		regexp.MustCompile(`/src/\w+\{/dir\}/\{file\}#L\{line\}$`),
		giteaURLTemplates, nil,
	},
	{
		regexp.MustCompile(`/-/blob/\w+\{/dir\}/\{file\}#L\{line\}$`),
		gitlab2URLTemplates, nil,
	},
	{
		regexp.MustCompile(`/tree\{/dir\}/\{file\}#n\{line\}$`),
		fdioURLTemplates, fdioTransformCommit,
	},
}
matchLegacyTemplates matches the templates from the go-source meta tag against some known patterns to guess the version-aware URL templates. If it can't find a match, it falls back using the go-source templates with some small replacements. These will not be version-aware but will still serve source at a fixed commit, which is better than nothing.
func ( context.Context,  *sourceMeta) ( urlTemplates,  transformCommitFunc) {
	if .fileTemplate == "" {
		return urlTemplates{}, nil
	}
	for ,  := range legacyTemplateMatches {
		if .fileRegexp.MatchString(.fileTemplate) {
			return .templates, .transformCommit
		}
	}
	log.Infof(, "matchLegacyTemplates: no matches for repo URL %q; replacing", .repoURL)
	 := strings.NewReplacer(
		"{/dir}/{file}", "/{file}",
		"{dir}/{file}", "{file}",
		"{/dir}", "/{dir}")
	 := .Replace(.fileTemplate)
	 := 
	if  := strings.LastIndexByte(, '#');  > 0 {
		 = [:]
	}
	return urlTemplates{
		Repo:      .repoURL,
		Directory: .Replace(.dirTemplate),
		File:      ,
		Line:      ,
	}, nil
}
adjustVersionedModuleDirectory changes info.moduleDir if necessary to correctly reflect the repo structure. info.moduleDir will be wrong if it has a suffix "/vN" for N > 1, and the repo uses the "major branch" convention, where modules at version 2 and higher live on branches rather than subdirectories. See https://research.swtch.com/vgo-module for a discussion of the "major branch" vs. "major subdirectory" conventions for organizing a repo.
func ( context.Context,  *Client,  *Info) {
	 := removeVersionSuffix(.moduleDir)
	if .moduleDir ==  {
		return
moduleDir does have a "/vN" for N > 1. To see if that is the actual directory, fetch the go.mod file from it.
On any failure, assume that the right directory is the one without the version.
	if  != nil {
		.moduleDir = 
	} else {
		.Body.Close()
	}
}
removeHTTPScheme removes an initial "http://" or "https://" from url. The result can be used to match against our static patterns. If the URL uses a different scheme, it won't be removed and it won't match any patterns, as intended.
func ( string) string {
	for ,  := range []string{"https://", "http://"} {
		if strings.HasPrefix(, ) {
			return [len():]
		}
	}
	return 
}
removeVersionSuffix returns s with "/vN" removed if N is an integer > 1. Otherwise it returns s.
func ( string) string {
	,  := path.Split()
	if !strings.HasPrefix(, "v") {
		return 
	}
	if ,  := strconv.Atoi([1:]);  != nil ||  < 2 {
		return 
	}
	return strings.TrimSuffix(, "/")
}

type transformCommitFunc func(commit string, isHash bool) string
Patterns for determining repo and URL templates from module paths or repo URLs. Each regexp must match a prefix of the target string, and must have a group named "repo".
var patterns = []struct {
	pattern   string // uncompiled regexp
	templates urlTemplates
transformCommit may alter the commit before substitution
	transformCommit transformCommitFunc
}{
	{
		pattern:   `^(?P<repo>github\.com/[a-z0-9A-Z_.\-]+/[a-z0-9A-Z_.\-]+)`,
		templates: githubURLTemplates,
	},
	{
		pattern:   `^(?P<repo>bitbucket\.org/[a-z0-9A-Z_.\-]+/[a-z0-9A-Z_.\-]+)`,
		templates: bitbucketURLTemplates,
	},
	{
		pattern:   `^(?P<repo>gitlab\.com/[a-z0-9A-Z_.\-]+/[a-z0-9A-Z_.\-]+)`,
		templates: githubURLTemplates,
	},
Assume that any site beginning with "gitlab." works like gitlab.com.
		pattern:   `^(?P<repo>gitlab\.[a-z0-9A-Z.-]+/[a-z0-9A-Z_.\-]+/[a-z0-9A-Z_.\-]+)(\.git|$)`,
		templates: githubURLTemplates,
	},
	{
		pattern:   `^(?P<repo>gitee\.com/[a-z0-9A-Z_.\-]+/[a-z0-9A-Z_.\-]+)(\.git|$)`,
		templates: githubURLTemplates,
	},
	{
		pattern: `^(?P<repo>git\.sr\.ht/~[a-z0-9A-Z_.\-]+/[a-z0-9A-Z_.\-]+)`,
		templates: urlTemplates{
			Directory: "{repo}/tree/{commit}/{dir}",
			File:      "{repo}/tree/{commit}/{file}",
			Line:      "{repo}/tree/{commit}/{file}#L{line}",
			Raw:       "{repo}/blob/{commit}/{file}",
		},
	},
	{
		pattern:         `^(?P<repo>git\.fd\.io/[a-z0-9A-Z_.\-]+)`,
		templates:       fdioURLTemplates,
		transformCommit: fdioTransformCommit,
	},
	{
		pattern:   `^(?P<repo>git\.pirl\.io/[a-z0-9A-Z_.\-]+/[a-z0-9A-Z_.\-]+)`,
		templates: gitlab2URLTemplates,
	},
	{
		pattern:         `^(?P<repo>gitea\.com/[a-z0-9A-Z_.\-]+/[a-z0-9A-Z_.\-]+)(\.git|$)`,
		templates:       giteaURLTemplates,
		transformCommit: giteaTransformCommit,
	},
Assume that any site beginning with "gitea." works like gitea.com.
		pattern:         `^(?P<repo>gitea\.[a-z0-9A-Z.-]+/[a-z0-9A-Z_.\-]+/[a-z0-9A-Z_.\-]+)(\.git|$)`,
		templates:       giteaURLTemplates,
		transformCommit: giteaTransformCommit,
	},
	{
		pattern:         `^(?P<repo>go\.isomorphicgo\.org/[a-z0-9A-Z_.\-]+/[a-z0-9A-Z_.\-]+)(\.git|$)`,
		templates:       giteaURLTemplates,
		transformCommit: giteaTransformCommit,
	},
	{
		pattern:         `^(?P<repo>git\.openprivacy\.ca/[a-z0-9A-Z_.\-]+/[a-z0-9A-Z_.\-]+)(\.git|$)`,
		templates:       giteaURLTemplates,
		transformCommit: giteaTransformCommit,
	},
	{
Gogs uses the same basic structure as Gitea, but omits the type of commit ("tag" or "commit"), so we don't need a transformCommit function. Gogs does not support short hashes, but we create those URLs anyway. See gogs/gogs#6242.
		templates: giteaURLTemplates,
	},
	{
		pattern: `^(?P<repo>dmitri\.shuralyov\.com\/.+)$`,
		templates: urlTemplates{
			Repo:      "{repo}/...",
			Directory: "https://gotools.org/{importPath}?rev={commit}",
			File:      "https://gotools.org/{importPath}?rev={commit}#{base}",
			Line:      "https://gotools.org/{importPath}?rev={commit}#{base}-L{line}",
		},
	},
	{
		pattern: `^(?P<repo>blitiri\.com\.ar/go/.+)$`,
		templates: urlTemplates{
			Repo:      "{repo}",
			Directory: "{repo}/b/master/t/{dir}",
			File:      "{repo}/b/master/t/{dir}f={file}.html",
			Line:      "{repo}/b/master/t/{dir}f={file}.html#line-{line}",
		},
	},
Patterns that match the general go command pattern, where they must have a ".git" repo suffix in an import path. If matching a repo URL from a meta tag, there is no ".git".
	{
		pattern:   `^(?P<repo>[^.]+\.googlesource\.com/[^.]+)(\.git|$)`,
		templates: googlesourceURLTemplates,
	},
	{
		pattern:   `^(?P<repo>git\.apache\.org/[^.]+)(\.git|$)`,
		templates: githubURLTemplates,
General syntax for the go command. We can extract the repo and directory, but we don't know the URL templates. Must be last in this list.
	{
		pattern:   `(?P<repo>([a-z0-9.\-]+\.)+[a-z0-9.\-]+(:[0-9]+)?(/~?[A-Za-z0-9_.\-]+)+?)\.(bzr|fossil|git|hg|svn)`,
		templates: urlTemplates{},
	},
}

func () {
	for  := range patterns {
The pattern regexp must contain a group named "repo".
		 := false
		for ,  := range .SubexpNames() {
			if  == "repo" {
				 = true
				break
			}
		}
		if ! {
			panic(fmt.Sprintf("pattern %s missing <repo> group", patterns[].pattern))
		}
		patterns[].re = 
	}
}
giteaTransformCommit transforms commits for the Gitea code hosting system.
Hashes use "commit", tags use "tag". Short hashes are supported as of v1.14.0.
	if  {
		return "commit/" + 
	}
	return "tag/" + 
}

hashes use "?id=", tags use "?h="
	 := "h"
	if  {
		 = "id"
	}
	return fmt.Sprintf("%s=%s", , )
}
urlTemplates describes how to build URLs from bits of source information. The fields are exported for JSON encoding. The template variables are: • {repo} - Repository URL with "https://" prefix ("https://example.com/myrepo"). • {importPath} - Package import path ("example.com/myrepo/mypkg"). • {commit} - Tag name or commit hash corresponding to version ("v0.1.0" or "1234567890ab"). • {dir} - Path to directory of the package, relative to repo root ("mypkg"). • {file} - Path to file containing the identifier, relative to repo root ("mypkg/file.go"). • {base} - Base name of file containing the identifier, including file extension ("file.go"). • {line} - Line number for the identifier ("41").
type urlTemplates struct {
	Repo      string `json:",omitempty"` // Optional URL template for the repository home page, with {repo}. If left empty, a default template "{repo}" is used.
	Directory string // URL template for a directory, with {repo}, {importPath}, {commit}, {dir}.
	File      string // URL template for a file, with {repo}, {importPath}, {commit}, {file}, {base}.
	Line      string // URL template for a line, with {repo}, {importPath}, {commit}, {file}, {base}, {line}.
	Raw       string // Optional URL template for the raw contents of a file, with {repo}, {commit}, {file}.
}

var (
	githubURLTemplates = urlTemplates{
		Directory: "{repo}/tree/{commit}/{dir}",
		File:      "{repo}/blob/{commit}/{file}",
		Line:      "{repo}/blob/{commit}/{file}#L{line}",
		Raw:       "{repo}/raw/{commit}/{file}",
	}

	bitbucketURLTemplates = urlTemplates{
		Directory: "{repo}/src/{commit}/{dir}",
		File:      "{repo}/src/{commit}/{file}",
		Line:      "{repo}/src/{commit}/{file}#lines-{line}",
		Raw:       "{repo}/raw/{commit}/{file}",
	}
	giteaURLTemplates = urlTemplates{
		Directory: "{repo}/src/{commit}/{dir}",
		File:      "{repo}/src/{commit}/{file}",
		Line:      "{repo}/src/{commit}/{file}#L{line}",
		Raw:       "{repo}/raw/{commit}/{file}",
	}
	googlesourceURLTemplates = urlTemplates{
		Directory: "{repo}/+/{commit}/{dir}",
		File:      "{repo}/+/{commit}/{file}",
Gitiles has no support for serving raw content at this time.
	}
	gitlab2URLTemplates = urlTemplates{
		Directory: "{repo}/-/tree/{commit}/{dir}",
		File:      "{repo}/-/blob/{commit}/{file}",
		Line:      "{repo}/-/blob/{commit}/{file}#L{line}",
		Raw:       "{repo}/-/raw/{commit}/{file}",
	}
	fdioURLTemplates = urlTemplates{
		Directory: "{repo}/tree/{dir}?{commit}",
		File:      "{repo}/tree/{file}?{commit}",
		Line:      "{repo}/tree/{file}?{commit}#n{line}",
		Raw:       "{repo}/plain/{file}?{commit}",
	}
	csopensourceTemplates = urlTemplates{
		Directory: "{repo}/+/{commit}:{dir}",
		File:      "{repo}/+/{commit}:{file}",
Gitiles has no support for serving raw content at this time.
	}
)
commitFromVersion returns a string that refers to a commit corresponding to version. It also reports whether the returned value is a commit hash. The string may be a tag, or it may be the hash or similar unique identifier of a commit. The second argument is the module path relative to the repo root.
Commit for the module: either a sha for pseudoversions, or a tag.
	 := strings.TrimSuffix(, "+incompatible")
Use the commit hash at the end.
		return [strings.LastIndex(, "-")+1:], true
The tags for a nested module begin with the relative module path of the module, removing a "/vN" suffix if N > 1.
		 := removeVersionSuffix()
		if  != "" {
			return  + "/" + , false
		}
		return , false
	}
}
trimVCSSuffix removes a VCS suffix from a repo URL in selected cases. The Go command allows a VCS suffix on a repo, like github.com/foo/bar.git. But some code hosting sites don't support all paths constructed from such URLs. For example, GitHub will redirect github.com/foo/bar.git to github.com/foo/bar, but will 404 on github.com/goo/bar.git/tree/master and any other URL with a non-empty path. To be conservative, we remove the suffix only in cases where we know it's wrong.
func ( string) string {
	if !strings.HasSuffix(, ".git") {
		return 
	}
	if strings.HasPrefix(, "https://github.com/") || strings.HasPrefix(, "https://gitlab.com/") {
		return strings.TrimSuffix(, ".git")
	}
	return 
}
The following code copied from cmd/go/internal/get:
expand rewrites s to replace {k} with match[k] for each key k in match.
We want to replace each match exactly once, and the result of expansion must not depend on the iteration order through the map. A strings.Replacer has exactly the properties we're looking for.
	 := make([]string, 0, 2*len())
	for ,  := range  {
		 = append(, "{"++"}", )
	}
	return strings.NewReplacer(...).Replace()
}
NewGitHubInfo creates a source.Info with GitHub URL templates. It is for testing only.
func (, ,  string) *Info {
	return &Info{
		repoURL:   trimVCSSuffix(),
		moduleDir: ,
		commit:    ,
		templates: githubURLTemplates,
	}
}
NewStdlibInfo returns a source.Info for the standard library at the given semantic version. It panics if the version does not correspond to a Go release tag. It is for testing only.
func ( string) *Info {
	,  := newStdlibInfo()
	if  != nil {
		panic()
	}
	return