cmd/go/internal/modfetch: translate commit hashes to known tags during stat

For convenience on the command line, and also when translating
existing dependency management metadata, we accept commit hashes
or other references and translate them into proper semantic versions.
To date, that conversion has always used a pseudo-version.
But since we are talking to the code repo to look up the commit,
we might as well check it against known tags and use one of those
if possible. So for example

	go get rsc.io/quote@23179ee

used to add "rsc.io/quote v0.0.0-20180214005840-23179ee8a569" to go.mod.
But that commit is tagged v1.5.1, so now the same command records the version
as v1.5.1 instead.

Change-Id: I274c3ef914c758e6e0e9f8f6cfad23062600a918
Reviewed-on: https://go-review.googlesource.com/121857
Reviewed-by: Bryan C. Mills <bcmills@google.com>
diff --git a/vendor/cmd/go/internal/modconv/convert_test.go b/vendor/cmd/go/internal/modconv/convert_test.go
index d2afcc5..811bbb1 100644
--- a/vendor/cmd/go/internal/modconv/convert_test.go
+++ b/vendor/cmd/go/internal/modconv/convert_test.go
@@ -95,7 +95,7 @@
 			require (
 				github.com/AdRoll/goamz v0.0.0-20150130162828-d3664b76d905
 				github.com/MSOpenTech/azure-sdk-for-go v0.0.0-20150323223030-d90753bcad2e
-				github.com/Sirupsen/logrus v0.0.0-20150409230825-55eb11d21d2a
+				github.com/Sirupsen/logrus v0.7.3
 				github.com/bugsnag/bugsnag-go v0.0.0-20141110184014-b1d153021fcd
 				github.com/bugsnag/osext v0.0.0-20130617224835-0dd3f918b21b
 				github.com/bugsnag/panicwrap v0.0.0-20141110184334-e5f9854865b9
diff --git a/vendor/cmd/go/internal/modfetch/codehost/codehost.go b/vendor/cmd/go/internal/modfetch/codehost/codehost.go
index 7a345db..f31653d 100644
--- a/vendor/cmd/go/internal/modfetch/codehost/codehost.go
+++ b/vendor/cmd/go/internal/modfetch/codehost/codehost.go
@@ -68,8 +68,9 @@
 type RevInfo struct {
 	Name    string    // complete ID in underlying repository
 	Short   string    // shortened ID, for use in pseudo-version
-	Version string    // TODO what is this?
+	Version string    // version used in lookup
 	Time    time.Time // commit time
+	Tags    []string  // known tags for commit
 }
 
 // AllHex reports whether the revision rev is entirely lower-case hexadecimal digits.
diff --git a/vendor/cmd/go/internal/modfetch/codehost/git.go b/vendor/cmd/go/internal/modfetch/codehost/git.go
index 66fdc0b..599d63e 100644
--- a/vendor/cmd/go/internal/modfetch/codehost/git.go
+++ b/vendor/cmd/go/internal/modfetch/codehost/git.go
@@ -309,18 +309,23 @@
 	defer r.mu.Unlock()
 
 	// If we know a specific commit we need, fetch it.
-	if r.fetchLevel <= fetchSome && hash != "" {
+	if r.fetchLevel <= fetchSome && hash != "" && !r.local {
 		r.fetchLevel = fetchSome
 		var refspec string
-		if ref != "" {
+		if ref != "" && ref != "head" {
 			// If we do know the ref name, save the mapping locally
 			// so that (if it is a tag) it can show up in localTags
 			// on a future call. Also, some servers refuse to allow
 			// full hashes in ref specs, so prefer a ref name if known.
 			refspec = ref + ":" + ref
 		} else {
+			// Fetch the hash but give it a local name (refs/dummy),
+			// because that triggers the fetch behavior of creating any
+			// other known remote tags for the hash. We never use
+			// refs/dummy (it's not refs/tags/dummy) and it will be
+			// overwritten in the next command, and that's fine.
 			ref = hash
-			refspec = hash
+			refspec = hash + ":refs/dummy"
 		}
 		_, err := Run(r.dir, "git", "fetch", "-f", "--depth=1", r.remote, refspec)
 		if err == nil {
@@ -357,12 +362,12 @@
 // statLocal returns a RevInfo describing rev in the local git repository.
 // It uses version as info.Version.
 func (r *gitRepo) statLocal(version, rev string) (*RevInfo, error) {
-	out, err := Run(r.dir, "git", "log", "-n1", "--format=format:%H %ct", rev)
+	out, err := Run(r.dir, "git", "log", "-n1", "--format=format:%H %ct %D", rev)
 	if err != nil {
 		return nil, fmt.Errorf("unknown revision %s", rev)
 	}
 	f := strings.Fields(string(out))
-	if len(f) != 2 {
+	if len(f) < 2 {
 		return nil, fmt.Errorf("unexpected response from git log: %q", out)
 	}
 	hash := f[0]
@@ -378,8 +383,30 @@
 		Name:    hash,
 		Short:   ShortenSHA1(hash),
 		Time:    time.Unix(t, 0).UTC(),
-		Version: version,
+		Version: hash,
 	}
+
+	// Add tags. Output looks like:
+	//	ede458df7cd0fdca520df19a33158086a8a68e81 1523994202 HEAD -> master, tag: v1.2.4-annotated, tag: v1.2.3, origin/master, origin/HEAD
+	for i := 2; i < len(f); i++ {
+		if f[i] == "tag:" {
+			i++
+			if i < len(f) {
+				info.Tags = append(info.Tags, strings.TrimSuffix(f[i], ","))
+			}
+		}
+	}
+	sort.Strings(info.Tags)
+
+	// Used hash as info.Version above.
+	// Use caller's suggested version if it appears in the tag list
+	// (filters out branch names, HEAD).
+	for _, tag := range info.Tags {
+		if version == tag {
+			info.Version = version
+		}
+	}
+
 	return info, nil
 }
 
diff --git a/vendor/cmd/go/internal/modfetch/codehost/git_test.go b/vendor/cmd/go/internal/modfetch/codehost/git_test.go
index 3d0f834..aa1328d 100644
--- a/vendor/cmd/go/internal/modfetch/codehost/git_test.go
+++ b/vendor/cmd/go/internal/modfetch/codehost/git_test.go
@@ -74,7 +74,7 @@
 
 func testRepo(remote string) (Repo, error) {
 	if remote == "localGitRepo" {
-		return LocalGitRepo("file://" + filepath.ToSlash(localGitRepo))
+		return LocalGitRepo(filepath.ToSlash(localGitRepo))
 	}
 	kind := "git"
 	for _, k := range []string{"hg"} {
@@ -135,6 +135,7 @@
 			Short:   "ede458df7cd0",
 			Version: "ede458df7cd0fdca520df19a33158086a8a68e81",
 			Time:    time.Date(2018, 4, 17, 19, 43, 22, 0, time.UTC),
+			Tags:    []string{"v1.2.3", "v1.2.4-annotated"},
 		},
 	},
 	{
@@ -162,7 +163,7 @@
 			if err != nil {
 				t.Fatal(err)
 			}
-			if *info != *tt.info {
+			if !reflect.DeepEqual(info, tt.info) {
 				t.Errorf("Latest: incorrect info\nhave %+v\nwant %+v", *info, *tt.info)
 			}
 		}
@@ -479,6 +480,7 @@
 			Short:   "ede458df7cd0",
 			Version: "ede458df7cd0fdca520df19a33158086a8a68e81",
 			Time:    time.Date(2018, 4, 17, 19, 43, 22, 0, time.UTC),
+			Tags:    []string{"v1.2.3", "v1.2.4-annotated"},
 		},
 	},
 	{
@@ -489,6 +491,7 @@
 			Short:   "9d02800338b8",
 			Version: "9d02800338b8a55be062c838d1f02e0c5780b9eb",
 			Time:    time.Date(2018, 4, 17, 20, 00, 32, 0, time.UTC),
+			Tags:    []string{"v2.0.2"},
 		},
 	},
 	{
@@ -499,6 +502,7 @@
 			Short:   "76a00fb249b7",
 			Version: "76a00fb249b7f93091bc2c89a789dab1fc1bc26f",
 			Time:    time.Date(2018, 4, 17, 19, 45, 48, 0, time.UTC),
+			Tags:    []string{"v2.0.1", "v2.3"},
 		},
 	},
 	{
@@ -509,6 +513,7 @@
 			Short:   "76a00fb249b7",
 			Version: "v2.3",
 			Time:    time.Date(2018, 4, 17, 19, 45, 48, 0, time.UTC),
+			Tags:    []string{"v2.0.1", "v2.3"},
 		},
 	},
 	{
@@ -519,6 +524,7 @@
 			Short:   "ede458df7cd0",
 			Version: "v1.2.3",
 			Time:    time.Date(2018, 4, 17, 19, 43, 22, 0, time.UTC),
+			Tags:    []string{"v1.2.3", "v1.2.4-annotated"},
 		},
 	},
 	{
@@ -529,6 +535,7 @@
 			Short:   "ede458df7cd0",
 			Version: "ede458df7cd0fdca520df19a33158086a8a68e81",
 			Time:    time.Date(2018, 4, 17, 19, 43, 22, 0, time.UTC),
+			Tags:    []string{"v1.2.3", "v1.2.4-annotated"},
 		},
 	},
 	{
@@ -549,6 +556,7 @@
 			Short:   "ede458df7cd0",
 			Version: "v1.2.4-annotated",
 			Time:    time.Date(2018, 4, 17, 19, 43, 22, 0, time.UTC),
+			Tags:    []string{"v1.2.3", "v1.2.4-annotated"},
 		},
 	},
 	{
@@ -581,7 +589,7 @@
 				}
 				return
 			}
-			if *info != *tt.info {
+			if !reflect.DeepEqual(info, tt.info) {
 				t.Errorf("Stat: incorrect info\nhave %+v\nwant %+v", *info, *tt.info)
 			}
 		}
diff --git a/vendor/cmd/go/internal/modfetch/codehost/vcs.go b/vendor/cmd/go/internal/modfetch/codehost/vcs.go
index 9c985f9..9b95da5 100644
--- a/vendor/cmd/go/internal/modfetch/codehost/vcs.go
+++ b/vendor/cmd/go/internal/modfetch/codehost/vcs.go
@@ -121,7 +121,7 @@
 		branchRE:      re(`(?m)^[^\n]+$`),
 		badLocalRevRE: re(`(?m)^(tip)$`),
 		statLocal: func(rev, remote string) []string {
-			return []string{"hg", "log", "-l1", "-r", rev, "--template", "{node} {date|hgdate}"}
+			return []string{"hg", "log", "-l1", "-r", rev, "--template", "{node} {date|hgdate} {tags}"}
 		},
 		parseStat: hgParseStat,
 		fetch:     []string{"hg", "pull", "-f"},
@@ -369,7 +369,7 @@
 
 func hgParseStat(rev, out string) (*RevInfo, error) {
 	f := strings.Fields(string(out))
-	if len(f) != 3 {
+	if len(f) < 3 {
 		return nil, fmt.Errorf("unexpected response from hg log: %q", out)
 	}
 	hash := f[0]
@@ -382,11 +382,20 @@
 		return nil, fmt.Errorf("invalid time from hg log: %q", out)
 	}
 
+	var tags []string
+	for _, tag := range f[3:] {
+		if tag != "tip" {
+			tags = append(tags, tag)
+		}
+	}
+	sort.Strings(tags)
+
 	info := &RevInfo{
 		Name:    hash,
 		Short:   ShortenSHA1(hash),
 		Time:    time.Unix(t, 0).UTC(),
 		Version: version,
+		Tags:    tags,
 	}
 	return info, nil
 }
diff --git a/vendor/cmd/go/internal/modfetch/coderepo.go b/vendor/cmd/go/internal/modfetch/coderepo.go
index 4c93e58..26f1fe8 100644
--- a/vendor/cmd/go/internal/modfetch/coderepo.go
+++ b/vendor/cmd/go/internal/modfetch/coderepo.go
@@ -128,7 +128,7 @@
 	if err != nil {
 		return nil, err
 	}
-	return r.convert(info)
+	return r.convert(info, rev)
 }
 
 func (r *codeRepo) Latest() (*RevInfo, error) {
@@ -136,33 +136,54 @@
 	if err != nil {
 		return nil, err
 	}
-	return r.convert(info)
+	return r.convert(info, "")
 }
 
-func (r *codeRepo) convert(info *codehost.RevInfo) (*RevInfo, error) {
-	versionOK := func(v string) bool {
-		return semver.IsValid(v) && v == semver.Canonical(v) && !IsPseudoVersion(v) && module.MatchPathMajor(v, r.pathMajor)
+func (r *codeRepo) convert(info *codehost.RevInfo, statVers string) (*RevInfo, error) {
+
+	info2 := &RevInfo{
+		Name:  info.Name,
+		Short: info.Short,
+		Time:  info.Time,
 	}
-	v := info.Version
-	if r.codeDir == "" {
-		if !versionOK(v) {
-			v = PseudoVersion(r.pseudoMajor, info.Time, info.Short)
-		}
+
+	// Determine version.
+	if semver.IsValid(statVers) && statVers == semver.Canonical(statVers) && module.MatchPathMajor(statVers, r.pathMajor) {
+		// The original call was repo.Stat(statVers), and requestedVersion is OK, so use it.
+		info2.Version = statVers
 	} else {
-		p := r.codeDir + "/"
-		if strings.HasPrefix(v, p) && versionOK(v[len(p):]) {
+		// Otherwise derive a version from a code repo tag.
+		// Tag must have a prefix matching codeDir.
+		p := ""
+		if r.codeDir != "" {
+			p = r.codeDir + "/"
+		}
+
+		tagOK := func(v string) bool {
+			if !strings.HasPrefix(v, p) {
+				return false
+			}
 			v = v[len(p):]
+			return semver.IsValid(v) && v == semver.Canonical(v) && module.MatchPathMajor(v, r.pathMajor) && !IsPseudoVersion(v)
+		}
+
+		// If info.Version is OK, use it.
+		if tagOK(info.Version) {
+			info2.Version = info.Version[len(p):]
 		} else {
-			v = PseudoVersion(r.pseudoMajor, info.Time, info.Short)
+			// Otherwise look through all known tags for latest in semver ordering.
+			for _, tag := range info.Tags {
+				if tagOK(tag) && semver.Compare(info2.Version, tag[len(p):]) < 0 {
+					info2.Version = tag[len(p):]
+				}
+			}
+			// Otherwise make a pseudo-version.
+			if info2.Version == "" {
+				info2.Version = PseudoVersion(r.pseudoMajor, info.Time, info.Short)
+			}
 		}
 	}
 
-	info2 := &RevInfo{
-		Name:    info.Name,
-		Short:   info.Short,
-		Time:    info.Time,
-		Version: v,
-	}
 	return info2, nil
 }
 
diff --git a/vendor/cmd/go/internal/modfetch/coderepo_test.go b/vendor/cmd/go/internal/modfetch/coderepo_test.go
index ac1b593..f8ad9e7 100644
--- a/vendor/cmd/go/internal/modfetch/coderepo_test.go
+++ b/vendor/cmd/go/internal/modfetch/coderepo_test.go
@@ -96,7 +96,7 @@
 	{
 		path:    "github.com/rsc/vgotest1",
 		rev:     "80d85c5",
-		version: "v0.0.0-20180219231006-80d85c5d4d17",
+		version: "v1.0.0",
 		name:    "80d85c5d4d17598a0e9055e7c175a32b415d6128",
 		short:   "80d85c5d4d17",
 		time:    time.Date(2018, 2, 19, 23, 10, 6, 0, time.UTC),
@@ -109,7 +109,7 @@
 	{
 		path:    "github.com/rsc/vgotest1",
 		rev:     "mytag",
-		version: "v0.0.0-20180219231006-80d85c5d4d17",
+		version: "v1.0.0",
 		name:    "80d85c5d4d17598a0e9055e7c175a32b415d6128",
 		short:   "80d85c5d4d17",
 		time:    time.Date(2018, 2, 19, 23, 10, 6, 0, time.UTC),
@@ -122,7 +122,7 @@
 	{
 		path:     "github.com/rsc/vgotest1/v2",
 		rev:      "80d85c5",
-		version:  "v2.0.0-20180219231006-80d85c5d4d17",
+		version:  "v2.0.0",
 		name:     "80d85c5d4d17598a0e9055e7c175a32b415d6128",
 		short:    "80d85c5d4d17",
 		time:     time.Date(2018, 2, 19, 23, 10, 6, 0, time.UTC),
@@ -270,7 +270,7 @@
 	{
 		path:    "gopkg.in/yaml.v2",
 		rev:     "d670f940",
-		version: "v2.0.0-20180109114331-d670f9405373",
+		version: "v2.0.0",
 		name:    "d670f9405373e636a5a2765eea47fac0c9bc91a4",
 		short:   "d670f9405373",
 		time:    time.Date(2018, 1, 9, 11, 43, 31, 0, time.UTC),
@@ -288,7 +288,7 @@
 	{
 		path:    "gopkg.in/yaml.v2",
 		rev:     "v2",
-		version: "v2.0.0-20180328195020-5420a8b6744d",
+		version: "v2.2.1",
 		name:    "5420a8b6744d3b0345ab293f6fcba19c978f1183",
 		short:   "5420a8b6744d",
 		time:    time.Date(2018, 3, 28, 19, 50, 20, 0, time.UTC),
@@ -297,7 +297,7 @@
 	{
 		path:    "vcs-test.golang.org/go/mod/gitrepo1",
 		rev:     "master",
-		version: "v0.0.0-20180417194322-ede458df7cd0",
+		version: "v1.2.4-annotated",
 		name:    "ede458df7cd0fdca520df19a33158086a8a68e81",
 		short:   "ede458df7cd0",
 		time:    time.Date(2018, 4, 17, 19, 43, 22, 0, time.UTC),
diff --git a/vendor/cmd/go/internal/modfetch/repo.go b/vendor/cmd/go/internal/modfetch/repo.go
index 46dec0d..00bbd27 100644
--- a/vendor/cmd/go/internal/modfetch/repo.go
+++ b/vendor/cmd/go/internal/modfetch/repo.go
@@ -327,7 +327,7 @@
 		return nil, nil, err
 	}
 
-	info, err := repo.(*codeRepo).convert(revInfo)
+	info, err := repo.(*codeRepo).convert(revInfo, "")
 	if err != nil {
 		return nil, nil, err
 	}
diff --git a/vendor/cmd/go/vgo_test.go b/vendor/cmd/go/vgo_test.go
index 070412b..218641c 100644
--- a/vendor/cmd/go/vgo_test.go
+++ b/vendor/cmd/go/vgo_test.go
@@ -431,6 +431,30 @@
 		t.Fatalf("expected golang.org/x/text indirect requirement:\n%s", gomod)
 	}
 
+	tg.run("-vgo", "get", "rsc.io/quote@v0.0.0-20180214005840-23179ee8a569") // should record as (time-corrected) pseudo-version
+	readGoMod()
+	if !strings.Contains(gomod, "rsc.io/quote v0.0.0-20180214005840-23179ee8a569\n") {
+		t.Fatalf("expected rsc.io/quote v0.0.0-20180214005840-23179ee8a569 (not v1.5.1)\n%s", gomod)
+	}
+
+	tg.run("-vgo", "get", "rsc.io/quote@23179ee") // should record as v1.5.1
+	readGoMod()
+	if !strings.Contains(gomod, "rsc.io/quote v1.5.1\n") {
+		t.Fatalf("expected rsc.io/quote v1.5.1 (not 23179ee)\n%s", gomod)
+	}
+
+	tg.run("-vgo", "mod", "-require", "rsc.io/quote@23179ee") // should record as 23179ee
+	readGoMod()
+	if !strings.Contains(gomod, "rsc.io/quote 23179ee\n") {
+		t.Fatalf("expected rsc.io/quote 23179ee\n%s", gomod)
+	}
+
+	tg.run("-vgo", "mod", "-fix") // fixup in any future vgo command should find v1.5.1 again
+	readGoMod()
+	if !strings.Contains(gomod, "rsc.io/quote v1.5.1\n") {
+		t.Fatalf("expected rsc.io/quote v1.5.1\n%s", gomod)
+	}
+
 	tg.run("-vgo", "get", "-m", "rsc.io/quote@dd9747d")
 	tg.run("-vgo", "list", "-m", "all")
 	tg.grepStdout(`quote v0.0.0-20180628003336-dd9747d19b04$`, "should have moved to pseudo-commit")
@@ -439,9 +463,9 @@
 	tg.run("-vgo", "list", "-m", "all")
 	tg.grepStdout(`quote v0.0.0-20180628003336-dd9747d19b04$`, "should have stayed on pseudo-commit")
 
-	tg.run("-vgo", "get", "-m", "rsc.io/quote@23179ee8a")
+	tg.run("-vgo", "get", "-m", "rsc.io/quote@e7a685a342")
 	tg.run("-vgo", "list", "-m", "all")
-	tg.grepStdout(`quote v0.0.0-20180214005840-23179ee8a569$`, "should have moved to new pseudo-commit")
+	tg.grepStdout(`quote v0.0.0-20180214005133-e7a685a342c0$`, "should have moved to new pseudo-commit")
 
 	tg.run("-vgo", "get", "-m", "-u")
 	tg.run("-vgo", "list", "-m", "all")