Browse Source

Support vN-unstable branches and tags.

Fixes #25.
master
Gustavo Niemeyer 7 years ago
parent
commit
cac38eedcd
4 changed files with 163 additions and 104 deletions
  1. +13
    -6
      main.go
  2. +25
    -8
      page.go
  3. +66
    -44
      version.go
  4. +59
    -46
      version_test.go

+ 13
- 6
main.go View File

@@ -115,8 +115,8 @@ func (repo *Repo) GopkgVersionRoot(version Version) string {
}
}

var patternOld = regexp.MustCompile(`^/(?:([a-z0-9][-a-z0-9]+)/)?((?:v0|v[1-9][0-9]*)(?:\.0|\.[1-9][0-9]*){0,2})/([a-zA-Z][-a-zA-Z0-9]*)(?:\.git)?((?:/[a-zA-Z][-a-zA-Z0-9]*)*)$`)
var patternNew = regexp.MustCompile(`^/(?:([a-zA-Z0-9][-a-zA-Z0-9]+)/)?([a-zA-Z][-.a-zA-Z0-9]*)\.((?:v0|v[1-9][0-9]*)(?:\.0|\.[1-9][0-9]*){0,2})(?:\.git)?((?:/[a-zA-Z0-9][-.a-zA-Z0-9]*)*)$`)
var patternOld = regexp.MustCompile(`^/(?:([a-z0-9][-a-z0-9]+)/)?((?:v0|v[1-9][0-9]*)(?:\.0|\.[1-9][0-9]*){0,2}(-unstable)?)/([a-zA-Z][-a-zA-Z0-9]*)(?:\.git)?((?:/[a-zA-Z][-a-zA-Z0-9]*)*)$`)
var patternNew = regexp.MustCompile(`^/(?:([a-zA-Z0-9][-a-zA-Z0-9]+)/)?([a-zA-Z][-.a-zA-Z0-9]*)\.((?:v0|v[1-9][0-9]*)(?:\.0|\.[1-9][0-9]*){0,2}(-unstable)?)(?:\.git)?((?:/[a-zA-Z0-9][-.a-zA-Z0-9]*)*)$`)

func handler(resp http.ResponseWriter, req *http.Request) {
if req.URL.Path == "/health-check" {
@@ -140,6 +140,7 @@ func handler(resp http.ResponseWriter, req *http.Request) {
sendNotFound(resp, "Unsupported URL pattern; see the documentation at gopkg.in for details.")
return
}
// "/v2/name" <= "/name.v2"
m[2], m[3] = m[3], m[2]
oldFormat = true
}
@@ -153,7 +154,7 @@ func handler(resp http.ResponseWriter, req *http.Request) {
repo := &Repo{
User: m[1],
Name: m[2],
SubPath: m[4],
SubPath: m[5],
OldFormat: oldFormat,
}

@@ -174,8 +175,14 @@ func handler(resp http.ResponseWriter, req *http.Request) {
sendNotFound(resp, "GitHub repository not found at https://%s", repo.GitHubRoot())
return
case ErrNoVersion:
v := repo.MajorVersion.String()
sendNotFound(resp, `GitHub repository at https://%s has no branch or tag "%s", "%s.N" or "%s.N.M"`, repo.GitHubRoot(), v, v, v)
major := repo.MajorVersion
suffix := ""
if major.Unstable {
major.Unstable = false
suffix = unstableSuffix
}
v := major.String()
sendNotFound(resp, `GitHub repository at https://%s has no branch or tag "%s%s", "%s.N%s" or "%s.N.M%s"`, repo.GitHubRoot(), v, suffix, v, suffix, v, suffix)
return
default:
resp.WriteHeader(http.StatusBadGateway)
@@ -306,7 +313,7 @@ func hackedRefs(repo *Repo) (data []byte, versions []Version, err error) {
}

// If there were absolutely no versions, and v0 was requested, accept the master as-is.
if len(versions) == 0 && repo.MajorVersion == (Version{0, -1, -1}) {
if len(versions) == 0 && repo.MajorVersion == (Version{0, -1, -1, false}) {
return data, nil, nil
}



+ 25
- 8
page.go View File

@@ -124,6 +124,11 @@ const packageTemplateString = `<!DOCTYPE html>
</div>
</div>
</div>
{{ if .Repo.MajorVersion.Unstable }}
<div class="col-sm-12 alert alert-danger">
This is an <b><i>unstable</i></b> package and should <i>not</i> be used in released code.
</div>
{{ end }}
<div class="row" >
<div class="col-sm-12" >
<a class="btn btn-lg btn-info" href="https://{{.Repo.GitHubRoot}}/tree/{{if .Repo.AllVersions}}{{.FullVersion}}{{else}}master{{end}}{{.Repo.SubPath}}" ><i class="fa fa-github"></i> Source Code</a>
@@ -153,7 +158,7 @@ const packageTemplateString = `<!DOCTYPE html>
{{ if .LatestVersions }}
{{ range .LatestVersions }}
<div>
<a href="//{{gopkgVersionRoot $.Repo .}}{{$.Repo.SubPath}}" {{if eq .Major $.Repo.MajorVersion.Major}}class="current"{{end}} >v{{.Major}}</a>
<a href="//{{gopkgVersionRoot $.Repo .}}{{$.Repo.SubPath}}" {{if eq .Major $.Repo.MajorVersion.Major}}{{if eq .Unstable $.Repo.MajorVersion.Unstable}}class="current"{{end}}{{end}}>v{{.Major}}{{if .Unstable}}-unstable{{end}}</a>
&rarr;
<span class="label label-default">{{.}}</span>
</div>
@@ -227,21 +232,33 @@ func renderPackagePage(resp http.ResponseWriter, req *http.Request, repo *Repo)
Repo: repo,
}

// calculate version mapping
latestVersionsMap := make(map[int]Version)
// Calculate the latest version for each major version, both stable and unstable.
latestVersions := make(map[int]Version)
latestUnstable := make(map[int]Version)
for _, v := range repo.AllVersions {
v2, exists := latestVersionsMap[v.Major]
m := latestVersions
if v.Unstable {
m = latestUnstable
}
v2, exists := m[v.Major]
if !exists || v2.Less(v) {
latestVersionsMap[v.Major] = v
m[v.Major] = v
}
}
data.FullVersion = latestVersionsMap[repo.MajorVersion.Major]
data.LatestVersions = make(VersionList, 0, len(latestVersionsMap))
for _, v := range latestVersionsMap {
data.LatestVersions = make(VersionList, 0, len(latestVersions))
for _, v := range latestVersions {
data.LatestVersions = append(data.LatestVersions, v)
}
sort.Sort(sort.Reverse(data.LatestVersions))

if repo.MajorVersion.Unstable {
data.FullVersion = latestUnstable[repo.MajorVersion.Major]
// Prepend post-sorting so it's show first.
data.LatestVersions = append([]Version{data.FullVersion}, data.LatestVersions...)
} else {
data.FullVersion = latestVersions[repo.MajorVersion.Major]
}

var dataMutex sync.Mutex
wantResps := 2
gotResp := make(chan bool, wantResps)


+ 66
- 44
version.go View File

@@ -7,20 +7,29 @@ import (
// Version represents a version number.
// An element that is not present is represented as -1.
type Version struct {
Major, Minor, Patch int
Major int
Minor int
Patch int
Unstable bool
}

const unstableSuffix = "-unstable"

func (v Version) String() string {
if v.Major < 0 {
panic(fmt.Sprintf("cannot stringify invalid version (major is %d)", v.Major))
}
suffix := ""
if v.Unstable {
suffix = unstableSuffix
}
if v.Minor < 0 {
return fmt.Sprintf("v%d", v.Major)
return fmt.Sprintf("v%d%s", v.Major, suffix)
}
if v.Patch < 0 {
return fmt.Sprintf("v%d.%d", v.Major, v.Minor)
return fmt.Sprintf("v%d.%d%s", v.Major, v.Minor, suffix)
}
return fmt.Sprintf("v%d.%d.%d", v.Major, v.Minor, v.Patch)
return fmt.Sprintf("v%d.%d.%d%s", v.Major, v.Minor, v.Patch, suffix)
}

// Less returns whether v is less than other.
@@ -31,7 +40,10 @@ func (v Version) Less(other Version) bool {
if v.Minor != other.Minor {
return v.Minor < other.Minor
}
return v.Patch < other.Patch
if v.Patch != other.Patch {
return v.Patch < other.Patch
}
return v.Unstable && !other.Unstable
}

// Contains returns whether version v contains version other.
@@ -40,7 +52,13 @@ func (v Version) Less(other Version) bool {
//
// For example, Version{1, 1, -1} contains both Version{1, 1, -1} and Version{1, 1, 2},
// but not Version{1, -1, -1} or Version{1, 2, -1}.
//
// Unstable versions (-unstable) only contain unstable versions, and stable
// versions only contain stable versions.
func (v Version) Contains(other Version) bool {
if v.Unstable != other.Unstable {
return false
}
if v.Patch != -1 {
return v == other
}
@@ -55,61 +73,65 @@ func (v Version) IsValid() bool {
}

// InvalidVersion represents a version that can't be parsed.
var InvalidVersion = Version{-1, -1, -1}
var InvalidVersion = Version{-1, -1, -1, false}

func parseVersion(s string) (Version, bool) {
func parseVersion(s string) (v Version, ok bool) {
v = InvalidVersion
if len(s) < 2 {
return InvalidVersion, false
return
}
if s[0] != 'v' {
return InvalidVersion, false
return
}
v := Version{-1, -1, -1}
vout := InvalidVersion
unstable := false
i := 1
v.Major, i = parseVersionPart(s, i)
if i < 0 {
return InvalidVersion, false
}
if i == len(s) {
return v, true
}
v.Minor, i = parseVersionPart(s, i)
if i < 0 {
return InvalidVersion, false
}
if i == len(s) {
return v, true
}
v.Patch, i = parseVersionPart(s, i)
if i < 0 || i < len(s) {
return InvalidVersion, false
for _, vptr := range []*int{&vout.Major, &vout.Minor, &vout.Patch} {
*vptr, unstable, i = parseVersionPart(s, i)
if i < 0 {
return
}
if i == len(s) {
vout.Unstable = unstable
return vout, true
}
}
return v, true
return
}

func parseVersionPart(s string, i int) (part int, newi int) {
dot := i
for dot < len(s) && s[dot] != '.' {
dot++
}
if dot == i || dot-i > 1 && s[i] == '0' {
return -1, -1
}
for i < len(s) {
if s[i] < '0' || s[i] > '9' {
return -1, -1
func parseVersionPart(s string, i int) (part int, unstable bool, newi int) {
j := i
for j < len(s) && s[j] != '.' && s[j] != '-' {
j++
}
if j == i || j-i > 1 && s[i] == '0' {
return -1, false, -1
}
c := s[i]
for {
if c < '0' || c > '9' {
return -1, false, -1
}
part *= 10
part += int(s[i] - '0')
part += int(c - '0')
if part < 0 {
return -1, -1
return -1, false, -1
}
i++
if i+1 < len(s) && s[i] == '.' {
return part, i + 1
if i == len(s) {
return part, false, i
}
c = s[i]
if i+1 < len(s) {
if c == '.' {
return part, false, i + 1
}
if c == '-' && s[i:] == unstableSuffix {
return part, true, i + len(unstableSuffix)
}
}
}
return part, i
panic("unreachable")
}

// VersionList implements sort.Interface


+ 59
- 46
version_test.go View File

@@ -13,31 +13,35 @@ var _ = Suite(&VersionSuite{})
type VersionSuite struct{}

var versionParseTests = []struct {
major, minor, patch int
s string
major int
minor int
patch int
dev bool
s string
}{
{-1, -1, -1, "v"},
{-1, -1, -1, "v-1"},
{-1, -1, -1, "v01"},
{-1, -1, -1, "v1.01"},
{-1, -1, -1, "a1"},
{-1, -1, -1, "v1a"},
{-1, -1, -1, "v1..2"},
{-1, -1, -1, "v1.2.3.4"},
{-1, -1, -1, "v1."},
{-1, -1, -1, "v1.2."},
{-1, -1, -1, "v1.2.3."},
{-1, -1, -1, false, "v"},
{-1, -1, -1, false, "v-1"},
{-1, -1, -1, false, "v-deb"},
{-1, -1, -1, false, "v01"},
{-1, -1, -1, false, "v1.01"},
{-1, -1, -1, false, "a1"},
{-1, -1, -1, false, "v1a"},
{-1, -1, -1, false, "v1..2"},
{-1, -1, -1, false, "v1.2.3.4"},
{-1, -1, -1, false, "v1."},
{-1, -1, -1, false, "v1.2."},
{-1, -1, -1, false, "v1.2.3."},

{0, -1, -1,
"v0"},
{1, -1, -1,
"v1"},
{1, 2, -1,
"v1.2"},
{1, 2, 3,
"v1.2.3"},
{12, 34, 56,
"v12.34.56"},
{0, -1, -1, false, "v0"},
{0, -1, -1, true, "v0-unstable"},
{1, -1, -1, false, "v1"},
{1, -1, -1, true, "v1-unstable"},
{1, 2, -1, false, "v1.2"},
{1, 2, -1, true, "v1.2-unstable"},
{1, 2, 3, false, "v1.2.3"},
{1, 2, 3, true, "v1.2.3-unstable"},
{12, 34, 56, false, "v12.34.56"},
{12, 34, 56, true, "v12.34.56-unstable"},
}

func (s *VersionSuite) TestParse(c *C) {
@@ -48,7 +52,7 @@ func (s *VersionSuite) TestParse(c *C) {
c.Fatalf("version %q is invalid but parsed as %#v", t.s, got)
}
} else {
want := Version{t.major, t.minor, t.patch}
want := Version{t.major, t.minor, t.patch, t.dev}
if got != want {
c.Fatalf("version %q must parse as %#v, got %#v", t.s, want, got)
}
@@ -61,24 +65,29 @@ func (s *VersionSuite) TestParse(c *C) {

var versionLessTests = []struct {
oneMajor, oneMinor, onePatch int
oneUnstable bool
twoMajor, twoMinor, twoPatch int
less bool
twoUnstable, less bool
}{
{0, 0, 0, 0, 0, 0, false},
{1, 0, 0, 1, 0, 0, false},
{1, 0, 0, 1, 1, 0, true},
{1, 0, 0, 2, 0, 0, true},
{0, 1, 0, 0, 1, 0, false},
{0, 1, 0, 0, 1, 1, true},
{0, 0, 0, 0, 2, 0, true},
{0, 0, 1, 0, 0, 1, false},
{0, 0, 1, 0, 0, 2, true},
{0, 0, 0, false, 0, 0, 0, false, false},
{1, 0, 0, false, 1, 0, 0, false, false},
{1, 0, 0, false, 1, 1, 0, false, true},
{1, 0, 0, false, 2, 0, 0, false, true},
{0, 1, 0, false, 0, 1, 0, false, false},
{0, 1, 0, false, 0, 1, 1, false, true},
{0, 0, 0, false, 0, 2, 0, false, true},
{0, 0, 1, false, 0, 0, 1, false, false},
{0, 0, 1, false, 0, 0, 2, false, true},

{0, 0, 0, false, 0, 0, 0, true, false},
{0, 0, 0, true, 0, 0, 0, false, true},
{0, 0, 1, true, 0, 0, 0, false, false},
}

func (s *VersionSuite) TestLess(c *C) {
for _, t := range versionLessTests {
one := Version{t.oneMajor, t.oneMinor, t.onePatch}
two := Version{t.twoMajor, t.twoMinor, t.twoPatch}
one := Version{t.oneMajor, t.oneMinor, t.onePatch, t.oneUnstable}
two := Version{t.twoMajor, t.twoMinor, t.twoPatch, t.twoUnstable}
if one.Less(two) != t.less {
c.Fatalf("version %s < %s returned %v", one, two, !t.less)
}
@@ -87,21 +96,25 @@ func (s *VersionSuite) TestLess(c *C) {

var versionContainsTests = []struct {
oneMajor, oneMinor, onePatch int
oneUnstable bool
twoMajor, twoMinor, twoPatch int
contains bool
twoUnstable, contains bool
}{
{12, 34, 56, 12, 34, 56, true},
{12, 34, 56, 12, 34, 78, false},
{12, 34, -1, 12, 34, 56, true},
{12, 34, -1, 12, 78, 56, false},
{12, -1, -1, 12, 34, 56, true},
{12, -1, -1, 78, 34, 56, false},
{12, 34, 56, false, 12, 34, 56, false, true},
{12, 34, 56, false, 12, 34, 78, false, false},
{12, 34, -1, false, 12, 34, 56, false, true},
{12, 34, -1, false, 12, 78, 56, false, false},
{12, -1, -1, false, 12, 34, 56, false, true},
{12, -1, -1, false, 78, 34, 56, false, false},

{12, -1, -1, true, 12, -1, -1, false, false},
{12, -1, -1, false, 12, -1, -1, true, false},
}

func (s *VersionSuite) TestContains(c *C) {
for _, t := range versionContainsTests {
one := Version{t.oneMajor, t.oneMinor, t.onePatch}
two := Version{t.twoMajor, t.twoMinor, t.twoPatch}
one := Version{t.oneMajor, t.oneMinor, t.onePatch, t.oneUnstable}
two := Version{t.twoMajor, t.twoMinor, t.twoPatch, t.twoUnstable}
if one.Contains(two) != t.contains {
c.Fatalf("version %s.Contains(%s) returned %v", one, two, !t.contains)
}
@@ -110,5 +123,5 @@ func (s *VersionSuite) TestContains(c *C) {

func (s *VersionSuite) TestIsValid(c *C) {
c.Assert(InvalidVersion.IsValid(), Equals, false)
c.Assert(Version{0, 0, 0}.IsValid(), Equals, true)
c.Assert(Version{0, 0, 0, false}.IsValid(), Equals, true)
}

Loading…
Cancel
Save