зеркало из https://github.com/golang/pkgsite.git
312 строки
11 KiB
Go
312 строки
11 KiB
Go
// 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 frontend
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"path"
|
|
"strings"
|
|
|
|
"golang.org/x/mod/module"
|
|
"golang.org/x/mod/semver"
|
|
"golang.org/x/pkgsite/internal"
|
|
"golang.org/x/pkgsite/internal/log"
|
|
"golang.org/x/pkgsite/internal/postgres"
|
|
"golang.org/x/pkgsite/internal/stdlib"
|
|
"golang.org/x/pkgsite/internal/version"
|
|
)
|
|
|
|
// VersionsDetails contains the hierarchy of version summary information used
|
|
// to populate the version tab. Version information is organized into separate
|
|
// lists, one for each (ModulePath, Major Version) pair.
|
|
type VersionsDetails struct {
|
|
// ThisModule is the slice of VersionLists with the same module path as the
|
|
// current package.
|
|
ThisModule []*VersionList
|
|
|
|
// OtherModules is the slice of VersionLists with a different module path
|
|
// from the current package.
|
|
OtherModules []*VersionList
|
|
}
|
|
|
|
// VersionListKey identifies a version list on the versions tab. We have a
|
|
// separate VersionList for each major version of a module series. Notably we
|
|
// have more version lists than module paths: v0 and v1 module versions are in
|
|
// separate version lists, despite having the same module path.
|
|
type VersionListKey struct {
|
|
// ModulePath is the module path of this major version.
|
|
ModulePath string
|
|
// Major is the major version string (e.g. v1, v2)
|
|
Major string
|
|
}
|
|
|
|
// VersionList holds all versions corresponding to a unique (module path,
|
|
// major version) tuple in the version hierarchy.
|
|
type VersionList struct {
|
|
VersionListKey
|
|
// Versions holds the nested version summaries, organized in descending
|
|
// semver order.
|
|
Versions []*VersionSummary
|
|
}
|
|
|
|
// VersionSummary holds data required to format the version link on the
|
|
// versions tab.
|
|
type VersionSummary struct {
|
|
CommitTime string
|
|
// Link to this version, for use in the anchor href.
|
|
Link string
|
|
Version string
|
|
}
|
|
|
|
func fetchVersionsDetails(ctx context.Context, ds internal.DataSource, fullPath, v1Path, modulePath string) (*VersionsDetails, error) {
|
|
db, ok := ds.(*postgres.DB)
|
|
if !ok {
|
|
// The proxydatasource does not support the imported by page.
|
|
return nil, proxydatasourceNotSupportedErr()
|
|
}
|
|
versions, err := db.GetVersionsForPath(ctx, fullPath)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
linkify := func(mi *internal.ModuleInfo) string {
|
|
// Here we have only version information, but need to construct the full
|
|
// import path of the package corresponding to this version.
|
|
var versionPath string
|
|
if mi.ModulePath == stdlib.ModulePath {
|
|
versionPath = fullPath
|
|
} else {
|
|
versionPath = pathInVersion(v1Path, mi)
|
|
}
|
|
return constructPackageURL(versionPath, mi.ModulePath, linkVersion(mi.Version, mi.ModulePath))
|
|
}
|
|
return buildVersionDetails(modulePath, versions, linkify), nil
|
|
}
|
|
|
|
func fetchModuleVersionsDetails(ctx context.Context, ds internal.DataSource, modulePath string) (*VersionsDetails, error) {
|
|
db, ok := ds.(*postgres.DB)
|
|
if !ok {
|
|
// The proxydatasource does not support the imported by page.
|
|
return nil, proxydatasourceNotSupportedErr()
|
|
}
|
|
versions, err := db.GetVersionsForPath(ctx, modulePath)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
linkify := func(m *internal.ModuleInfo) string {
|
|
return constructModuleURL(m.ModulePath, linkVersion(m.Version, m.ModulePath))
|
|
}
|
|
return buildVersionDetails(modulePath, versions, linkify), nil
|
|
}
|
|
|
|
// legacyFetchModuleVersionsDetails builds a version hierarchy for module versions
|
|
// with the same series path as the given version.
|
|
func legacyFetchModuleVersionsDetails(ctx context.Context, ds internal.DataSource, mi *internal.ModuleInfo) (*VersionsDetails, error) {
|
|
versions, err := ds.LegacyGetTaggedVersionsForModule(ctx, mi.ModulePath)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
// If no tagged versions of the module are found, fetch pseudo-versions
|
|
// instead.
|
|
if len(versions) == 0 {
|
|
versions, err = ds.LegacyGetPsuedoVersionsForModule(ctx, mi.ModulePath)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
linkify := func(m *internal.ModuleInfo) string {
|
|
return constructModuleURL(m.ModulePath, linkVersion(m.Version, m.ModulePath))
|
|
}
|
|
return buildVersionDetails(mi.ModulePath, versions, linkify), nil
|
|
}
|
|
|
|
// legacyFetchPackageVersionsDetails builds a version hierarchy for all module
|
|
// versions containing a package path with v1 import path matching the given v1 path.
|
|
func legacyFetchPackageVersionsDetails(ctx context.Context, ds internal.DataSource, pkgPath, v1Path, modulePath string) (*VersionsDetails, error) {
|
|
versions, err := ds.LegacyGetTaggedVersionsForPackageSeries(ctx, pkgPath)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
// If no tagged versions for the package series are found, fetch the
|
|
// pseudo-versions instead.
|
|
if len(versions) == 0 {
|
|
versions, err = ds.LegacyGetPsuedoVersionsForPackageSeries(ctx, pkgPath)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
|
|
linkify := func(mi *internal.ModuleInfo) string {
|
|
// Here we have only version information, but need to construct the full
|
|
// import path of the package corresponding to this version.
|
|
var versionPath string
|
|
if mi.ModulePath == stdlib.ModulePath {
|
|
versionPath = pkgPath
|
|
} else {
|
|
versionPath = pathInVersion(v1Path, mi)
|
|
}
|
|
return constructPackageURL(versionPath, mi.ModulePath, linkVersion(mi.Version, mi.ModulePath))
|
|
}
|
|
return buildVersionDetails(modulePath, versions, linkify), nil
|
|
}
|
|
|
|
// pathInVersion constructs the full import path of the package corresponding
|
|
// to mi, given its v1 path. To do this, we first compute the suffix of the
|
|
// package path in the given module series, and then append it to the real
|
|
// (versioned) module path.
|
|
//
|
|
// For example: if we're considering package foo.com/v3/bar/baz, and encounter
|
|
// module version foo.com/bar/v2, we do the following:
|
|
// 1) Start with the v1Path foo.com/bar/baz.
|
|
// 2) Trim off the version series path foo.com/bar to get 'baz'.
|
|
// 3) Join with the versioned module path foo.com/bar/v2 to get
|
|
// foo.com/bar/v2/baz.
|
|
// ...being careful about slashes along the way.
|
|
func pathInVersion(v1Path string, mi *internal.ModuleInfo) string {
|
|
suffix := internal.Suffix(v1Path, mi.SeriesPath())
|
|
if suffix == "" {
|
|
return mi.ModulePath
|
|
}
|
|
return path.Join(mi.ModulePath, suffix)
|
|
}
|
|
|
|
// buildVersionDetails constructs the version hierarchy to be rendered on the
|
|
// versions tab, organizing major versions into those that have the same module
|
|
// path as the package version under consideration, and those that don't. The
|
|
// given versions MUST be sorted first by module path and then by semver.
|
|
func buildVersionDetails(currentModulePath string, modInfos []*internal.ModuleInfo, linkify func(v *internal.ModuleInfo) string) *VersionsDetails {
|
|
|
|
// lists organizes versions by VersionListKey. Note that major version isn't
|
|
// sufficient as a key: there are packages contained in the same major
|
|
// version of different modules, for example github.com/hashicorp/vault/api,
|
|
// which exists in v1 of both of github.com/hashicorp/vault and
|
|
// github.com/hashicorp/vault/api.
|
|
lists := make(map[VersionListKey][]*VersionSummary)
|
|
// seenLists tracks the order in which we encounter entries of each version
|
|
// list. We want to preserve this order.
|
|
var seenLists []VersionListKey
|
|
for _, mi := range modInfos {
|
|
// Try to resolve the most appropriate major version for this version. If
|
|
// we detect a +incompatible version (when the path version does not match
|
|
// the sematic version), we prefer the path version.
|
|
major := semver.Major(mi.Version)
|
|
if mi.ModulePath == stdlib.ModulePath {
|
|
var err error
|
|
major, err = stdlib.MajorVersionForVersion(mi.Version)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
}
|
|
if _, pathMajor, ok := module.SplitPathVersion(mi.ModulePath); ok {
|
|
// We prefer the path major version except for v1 import paths where the
|
|
// semver major version is v0. In this case, we prefer the more specific
|
|
// semver version.
|
|
if pathMajor != "" {
|
|
// Trim both '/' and '.' from the path major version to account for
|
|
// standard and gopkg.in module paths.
|
|
major = strings.TrimLeft(pathMajor, "/.")
|
|
} else if major != "v0" && !strings.HasPrefix(major, "go") {
|
|
major = "v1"
|
|
}
|
|
}
|
|
key := VersionListKey{ModulePath: mi.ModulePath, Major: major}
|
|
vs := &VersionSummary{
|
|
Link: linkify(mi),
|
|
CommitTime: elapsedTime(mi.CommitTime),
|
|
Version: linkVersion(mi.Version, mi.ModulePath),
|
|
}
|
|
if _, ok := lists[key]; !ok {
|
|
seenLists = append(seenLists, key)
|
|
}
|
|
lists[key] = append(lists[key], vs)
|
|
}
|
|
|
|
var details VersionsDetails
|
|
for _, key := range seenLists {
|
|
vl := &VersionList{
|
|
VersionListKey: key,
|
|
Versions: lists[key],
|
|
}
|
|
if key.ModulePath == currentModulePath {
|
|
details.ThisModule = append(details.ThisModule, vl)
|
|
} else {
|
|
details.OtherModules = append(details.OtherModules, vl)
|
|
}
|
|
}
|
|
return &details
|
|
}
|
|
|
|
// formatVersion formats a more readable representation of the given version
|
|
// string. On any parsing error, it simply returns the input unmodified.
|
|
//
|
|
// For prerelease versions, formatVersion separates the prerelease portion of
|
|
// the version string into a parenthetical. i.e.
|
|
// formatVersion("v1.2.3-alpha") = "v1.2.3 (alpha)"
|
|
//
|
|
// For pseudo versions, formatVersion uses a short commit hash to identify the
|
|
// version. i.e.
|
|
// formatVersion("v1.2.3-20190311183353-d8887717615a") = "v.1.2.3 (d888771)"
|
|
func formatVersion(v string) string {
|
|
vType, err := version.ParseType(v)
|
|
if err != nil {
|
|
log.Errorf(context.TODO(), "Error parsing version %q: %v", v, err)
|
|
return v
|
|
}
|
|
pre := semver.Prerelease(v)
|
|
base := strings.TrimSuffix(v, pre)
|
|
pre = strings.TrimPrefix(pre, "-")
|
|
switch vType {
|
|
case version.TypePrerelease:
|
|
return fmt.Sprintf("%s (%s)", base, pre)
|
|
case version.TypePseudo:
|
|
rev := pseudoVersionRev(v)
|
|
commitLen := 7
|
|
if len(rev) < commitLen {
|
|
commitLen = len(rev)
|
|
}
|
|
return fmt.Sprintf("%s (%s)", base, rev[0:commitLen])
|
|
default:
|
|
return v
|
|
}
|
|
}
|
|
|
|
// pseudoVersionRev extracts the commit identifier from a pseudo version
|
|
// string. It assumes the pseudo version is correctly formatted.
|
|
func pseudoVersionRev(v string) string {
|
|
v = strings.TrimSuffix(v, "+incompatible")
|
|
j := strings.LastIndex(v, "-")
|
|
return v[j+1:]
|
|
}
|
|
|
|
// displayVersion returns the version string, formatted for display.
|
|
func displayVersion(v string, modulePath string) string {
|
|
if modulePath == stdlib.ModulePath {
|
|
return goTagForVersion(v)
|
|
}
|
|
return formatVersion(v)
|
|
}
|
|
|
|
// linkVersion returns the version string, suitable for use in
|
|
// a link to this site.
|
|
func linkVersion(v string, modulePath string) string {
|
|
if modulePath == stdlib.ModulePath {
|
|
return goTagForVersion(v)
|
|
}
|
|
return v
|
|
}
|
|
|
|
// goTagForVersion returns the Go tag corresponding to a given semantic
|
|
// version. It should only be used if we are 100% sure the version will
|
|
// correspond to a Go tag, such as when we are fetching the version from the
|
|
// database.
|
|
func goTagForVersion(v string) string {
|
|
tag, err := stdlib.TagForVersion(v)
|
|
if err != nil {
|
|
log.Errorf(context.TODO(), "goTagForVersion(%q): %v", v, err)
|
|
return "unknown"
|
|
}
|
|
return tag
|
|
}
|