internal/stdlib: added ability to fetch master version

Added the ability to fetch the master version of a module

Fixes golang/go#39973

Change-Id: I66ea0dc3a2249ac4757a3d3b478c428f13c2ec89
Reviewed-on: https://go-review.googlesource.com/c/pkgsite/+/279292
Reviewed-by: Jonathan Amsterdam <jba@google.com>
Trust: Julie Qiu <julie@golang.org>
diff --git a/internal/stdlib/stdlib.go b/internal/stdlib/stdlib.go
index 44f456d..5b96c35 100644
--- a/internal/stdlib/stdlib.go
+++ b/internal/stdlib/stdlib.go
@@ -48,12 +48,15 @@
 )
 
 // VersionForTag returns the semantic version for the Go tag, or "" if
-// tag doesn't correspond to a Go release or beta tag.
+// tag doesn't correspond to a Go release or beta tag. In special cases,
+// when the tag specified is either `latest` or `master` it will return the tag.
 // Examples:
 //   "go1" => "v1.0.0"
 //   "go1.2" => "v1.2.0"
 //   "go1.13beta1" => "v1.13.0-beta.1"
 //   "go1.9rc2" => "v1.9.0-rc.2"
+//   "latest" => "latest"
+//   "master" => "master"
 func VersionForTag(tag string) string {
 	// Special cases for go1.
 	if tag == "go1" {
@@ -62,9 +65,9 @@
 	if tag == "go1.0" {
 		return ""
 	}
-	// Special case for latest.
-	if tag == "latest" {
-		return "latest"
+	// Special case for latest and master.
+	if tag == "latest" || tag == "master" {
+		return tag
 	}
 	m := tagRegexp.FindStringSubmatch(tag)
 	if m == nil {
@@ -88,6 +91,11 @@
 func TagForVersion(version string) (_ string, err error) {
 	defer derrors.Wrap(&err, "TagForVersion(%q)", version)
 
+	// Special case: master => master
+	if version == "master" {
+		return version, nil
+	}
+
 	// Special case: v1.0.0 => go1.
 	if version == "v1.0.0" {
 		return "go1", nil
@@ -238,9 +246,6 @@
 
 	var versions []string
 	for _, name := range refNames {
-		if !name.IsTag() {
-			continue
-		}
 		v := VersionForTag(name.Short())
 		if v != "" {
 			versions = append(versions, v)
@@ -251,11 +256,11 @@
 
 // Directory returns the directory of the standard library relative to the repo root.
 func Directory(version string) string {
-	// For versions older than v1.4.0-beta.1, the stdlib is in src/pkg.
-	if semver.Compare(version, "v1.4.0-beta.1") < 0 {
-		return "src/pkg"
+	if semver.Compare(version, "v1.4.0-beta.1") >= 0 || version == "master" {
+		return "src"
 	}
-	return "src"
+	// For versions older than v1.4.0-beta.1, the stdlib is in src/pkg.
+	return "src/pkg"
 }
 
 // Approximate size of Zip("v1.15.2").
@@ -346,9 +351,14 @@
 	if err != nil {
 		return "", err
 	}
-	if requestedVersion == "latest" {
+
+	switch requestedVersion {
+	case "latest":
 		var latestVersion string
 		for _, v := range knownVersions {
+			if !strings.HasPrefix(v, "v") {
+				continue
+			}
 			versionType, err := version.ParseType(v)
 			if err != nil {
 				return "", err
@@ -362,13 +372,16 @@
 			}
 		}
 		return latestVersion, nil
-	}
-
-	for _, v := range knownVersions {
-		if v == requestedVersion {
-			return requestedVersion, nil
+	case "master":
+		return requestedVersion, nil
+	default:
+		for _, v := range knownVersions {
+			if v == requestedVersion {
+				return requestedVersion, nil
+			}
 		}
 	}
+
 	return "", fmt.Errorf("%w: requested version unknown: %q", derrors.InvalidArgument, requestedVersion)
 }
 
@@ -484,6 +497,7 @@
 	"refs/tags/go1.13",
 	"refs/tags/go1.13beta1",
 	"refs/tags/go1.14.6",
+	"refs/heads/master",
 	// other tags
 	"refs/changes/56/93156/13",
 	"refs/tags/release.r59",
diff --git a/internal/stdlib/stdlib_test.go b/internal/stdlib/stdlib_test.go
index fec1e73..0b24f39 100644
--- a/internal/stdlib/stdlib_test.go
+++ b/internal/stdlib/stdlib_test.go
@@ -56,6 +56,11 @@
 			want:    "go1.13",
 		},
 		{
+			name:    "master branch",
+			version: "master",
+			want:    "master",
+		},
+		{
 			name:    "bad std semver",
 			version: "v1.x",
 			wantErr: true,
@@ -114,8 +119,7 @@
 func TestZip(t *testing.T) {
 	UseTestData = true
 	defer func() { UseTestData = false }()
-
-	for _, resolvedVersion := range []string{"v1.14.6", "v1.12.5", "v1.3.2"} {
+	for _, resolvedVersion := range []string{"v1.14.6", "v1.12.5", "v1.3.2", "master"} {
 		t.Run(resolvedVersion, func(t *testing.T) {
 			zr, gotTime, err := Zip(resolvedVersion)
 			if err != nil {
@@ -129,12 +133,12 @@
 				"errors/errors.go":      true,
 				"errors/errors_test.go": true,
 			}
-			if semver.Compare(resolvedVersion, "v1.4.0") > 0 {
+			if semver.Compare(resolvedVersion, "v1.4.0") > 0 || resolvedVersion == "master" {
 				wantFiles["README.md"] = true
 			} else {
 				wantFiles["README"] = true
 			}
-			if semver.Compare(resolvedVersion, "v1.13.0") > 0 {
+			if semver.Compare(resolvedVersion, "v1.13.0") > 0 || resolvedVersion == "master" {
 				wantFiles["cmd/README.vendor"] = true
 			}
 
@@ -178,15 +182,29 @@
 	UseTestData = true
 	defer func() { UseTestData = false }()
 
-	gotVersion, gotSize, err := ZipInfo("latest")
-	if err != nil {
-		t.Fatal(err)
-	}
-	if want := "v1.14.6"; gotVersion != want {
-		t.Errorf("version: got %q, want %q", gotVersion, want)
-	}
-	if want := int64(estimatedZipSize); gotSize != want {
-		t.Errorf("size: got %d, want %d", gotSize, want)
+	for _, tc := range []struct {
+		requestedVersion string
+		want             string
+	}{
+		{
+			requestedVersion: "latest",
+			want:             "v1.14.6",
+		},
+		{
+			requestedVersion: "master",
+			want:             "master",
+		},
+	} {
+		gotVersion, gotSize, err := ZipInfo(tc.requestedVersion)
+		if err != nil {
+			t.Fatal(err)
+		}
+		if want := tc.want; gotVersion != want {
+			t.Errorf("version: got %q, want %q", gotVersion, want)
+		}
+		if want := int64(estimatedZipSize); gotSize != want {
+			t.Errorf("size: got %d, want %d", gotSize, want)
+		}
 	}
 }
 
@@ -255,3 +273,28 @@
 		}
 	}
 }
+
+func TestDirectory(t *testing.T) {
+	for _, tc := range []struct {
+		version string
+		want    string
+	}{
+		{
+			version: "v1.3.0-beta2",
+			want:    "src/pkg",
+		},
+		{
+			version: "v1.16.0-beta1",
+			want:    "src",
+		},
+		{
+			version: "master",
+			want:    "src",
+		},
+	} {
+		got := Directory(tc.version)
+		if got != tc.want {
+			t.Errorf("Directory(%s) = %s, want %s", tc.version, got, tc.want)
+		}
+	}
+}
diff --git a/internal/stdlib/testdata/master/LICENSE b/internal/stdlib/testdata/master/LICENSE
new file mode 100644
index 0000000..6a66aea
--- /dev/null
+++ b/internal/stdlib/testdata/master/LICENSE
@@ -0,0 +1,27 @@
+Copyright (c) 2009 The Go Authors. All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
+
+   * Redistributions of source code must retain the above copyright
+notice, this list of conditions and the following disclaimer.
+   * Redistributions in binary form must reproduce the above
+copyright notice, this list of conditions and the following disclaimer
+in the documentation and/or other materials provided with the
+distribution.
+   * Neither the name of Google Inc. nor the names of its
+contributors may be used to endorse or promote products derived from
+this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/internal/stdlib/testdata/master/README.md b/internal/stdlib/testdata/master/README.md
new file mode 100644
index 0000000..d6d2b9d
--- /dev/null
+++ b/internal/stdlib/testdata/master/README.md
@@ -0,0 +1 @@
+# The Go Programming Language
diff --git a/internal/stdlib/testdata/master/src/README.vendor b/internal/stdlib/testdata/master/src/README.vendor
new file mode 100644
index 0000000..e74fc2f
--- /dev/null
+++ b/internal/stdlib/testdata/master/src/README.vendor
@@ -0,0 +1,54 @@
+Vendoring in std and cmd
+========================
+
+The Go command maintains copies of external packages needed by the
+standard library in the src/vendor and src/cmd/vendor directories.
+
+In GOPATH mode, imports of vendored packages are resolved to these
+directories following normal vendor directory logic
+(see golang.org/s/go15vendor).
+
+In module mode, std and cmd are modules (defined in src/go.mod and
+src/cmd/go.mod). When a package outside std or cmd is imported
+by a package inside std or cmd, the import path is interpreted
+as if it had a "vendor/" prefix. For example, within "crypto/tls",
+an import of "golang.org/x/crypto/cryptobyte" resolves to
+"vendor/golang.org/x/crypto/cryptobyte". When a package with the
+same path is imported from a package outside std or cmd, it will
+be resolved normally. Consequently, a binary may be built with two
+copies of a package at different versions if the package is
+imported normally and vendored by the standard library.
+
+Vendored packages are internally renamed with a "vendor/" prefix
+to preserve the invariant that all packages have distinct paths.
+This is necessary to avoid compiler and linker conflicts. Adding
+a "vendor/" prefix also maintains the invariant that standard
+library packages begin with a dotless path element.
+
+The module requirements of std and cmd do not influence version
+selection in other modules. They are only considered when running
+module commands like 'go get' and 'go mod vendor' from a directory
+in GOROOT/src.
+
+Maintaining vendor directories
+==============================
+
+Before updating vendor directories, ensure that module mode is enabled.
+Make sure GO111MODULE=off is not set ('on' or 'auto' should work).
+
+Requirements may be added, updated, and removed with 'go get'.
+The vendor directory may be updated with 'go mod vendor'.
+A typical sequence might be:
+
+    cd src
+    go get -d golang.org/x/net@latest
+    go mod tidy
+    go mod vendor
+
+Use caution when passing '-u' to 'go get'. The '-u' flag updates
+modules providing all transitively imported packages, not only
+the module providing the target package.
+
+Note that 'go mod vendor' only copies packages that are transitively
+imported by packages in the current module. If a new package is needed,
+it should be imported before running 'go mod vendor'.
diff --git a/internal/stdlib/testdata/master/src/cmd/README.vendor b/internal/stdlib/testdata/master/src/cmd/README.vendor
new file mode 100644
index 0000000..ac0df5e
--- /dev/null
+++ b/internal/stdlib/testdata/master/src/cmd/README.vendor
@@ -0,0 +1,2 @@
+See src/README.vendor for information on loading vendored packages
+and updating the vendor directory.
diff --git a/internal/stdlib/testdata/master/src/errors/errors.go b/internal/stdlib/testdata/master/src/errors/errors.go
new file mode 100644
index 0000000..b8a4692
--- /dev/null
+++ b/internal/stdlib/testdata/master/src/errors/errors.go
@@ -0,0 +1,20 @@
+// Copyright 2011 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 errors implements functions to manipulate errors.
+package errors
+
+// New returns an error that formats as the given text.
+func New(text string) error {
+	return &errorString{text}
+}
+
+// errorString is a trivial implementation of error.
+type errorString struct {
+	s string
+}
+
+func (e *errorString) Error() string {
+	return e.s
+}
diff --git a/internal/stdlib/testdata/master/src/errors/errors_test.go b/internal/stdlib/testdata/master/src/errors/errors_test.go
new file mode 100644
index 0000000..cf4df90
--- /dev/null
+++ b/internal/stdlib/testdata/master/src/errors/errors_test.go
@@ -0,0 +1,53 @@
+// Copyright 2011 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 errors_test
+
+import (
+	"errors"
+	"fmt"
+	"testing"
+)
+
+func TestNewEqual(t *testing.T) {
+	// Different allocations should not be equal.
+	if errors.New("abc") == errors.New("abc") {
+		t.Errorf(`New("abc") == New("abc")`)
+	}
+	if errors.New("abc") == errors.New("xyz") {
+		t.Errorf(`New("abc") == New("xyz")`)
+	}
+
+	// Same allocation should be equal to itself (not crash).
+	err := errors.New("jkl")
+	if err != err {
+		t.Errorf(`err != err`)
+	}
+}
+
+func TestErrorMethod(t *testing.T) {
+	err := errors.New("abc")
+	if err.Error() != "abc" {
+		t.Errorf(`New("abc").Error() = %q, want %q`, err.Error(), "abc")
+	}
+}
+
+func ExampleNew() {
+	err := errors.New("emit macho dwarf: elf header corrupted")
+	if err != nil {
+		fmt.Print(err)
+	}
+	// Output: emit macho dwarf: elf header corrupted
+}
+
+// The fmt package's Errorf function lets us use the package's formatting
+// features to create descriptive error messages.
+func ExampleNew_errorf() {
+	const name, id = "bimmler", 17
+	err := fmt.Errorf("user %q (id %d) not found", name, id)
+	if err != nil {
+		fmt.Print(err)
+	}
+	// Output: user "bimmler" (id 17) not found
+}