Browse Source

Support vN-unstable branches and tags.

Fixes #25.
Gustavo Niemeyer 4 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 {
115 115
 	}
116 116
 }
117 117
 
118
-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]*)*)$`)
119
-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]*)*)$`)
118
+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]*)*)$`)
119
+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]*)*)$`)
120 120
 
121 121
 func handler(resp http.ResponseWriter, req *http.Request) {
122 122
 	if req.URL.Path == "/health-check" {
@@ -140,6 +140,7 @@ func handler(resp http.ResponseWriter, req *http.Request) {
140 140
 			sendNotFound(resp, "Unsupported URL pattern; see the documentation at gopkg.in for details.")
141 141
 			return
142 142
 		}
143
+		// "/v2/name" <= "/name.v2"
143 144
 		m[2], m[3] = m[3], m[2]
144 145
 		oldFormat = true
145 146
 	}
@@ -153,7 +154,7 @@ func handler(resp http.ResponseWriter, req *http.Request) {
153 154
 	repo := &Repo{
154 155
 		User:      m[1],
155 156
 		Name:      m[2],
156
-		SubPath:   m[4],
157
+		SubPath:   m[5],
157 158
 		OldFormat: oldFormat,
158 159
 	}
159 160
 
@@ -174,8 +175,14 @@ func handler(resp http.ResponseWriter, req *http.Request) {
174 175
 		sendNotFound(resp, "GitHub repository not found at https://%s", repo.GitHubRoot())
175 176
 		return
176 177
 	case ErrNoVersion:
177
-		v := repo.MajorVersion.String()
178
-		sendNotFound(resp, `GitHub repository at https://%s has no branch or tag "%s", "%s.N" or "%s.N.M"`, repo.GitHubRoot(), v, v, v)
178
+		major := repo.MajorVersion
179
+		suffix := ""
180
+		if major.Unstable {
181
+			major.Unstable = false
182
+			suffix = unstableSuffix
183
+		}
184
+		v := major.String()
185
+		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)
179 186
 		return
180 187
 	default:
181 188
 		resp.WriteHeader(http.StatusBadGateway)
@@ -306,7 +313,7 @@ func hackedRefs(repo *Repo) (data []byte, versions []Version, err error) {
306 313
 	}
307 314
 
308 315
 	// If there were absolutely no versions, and v0 was requested, accept the master as-is.
309
-	if len(versions) == 0 && repo.MajorVersion == (Version{0, -1, -1}) {
316
+	if len(versions) == 0 && repo.MajorVersion == (Version{0, -1, -1, false}) {
310 317
 		return data, nil, nil
311 318
 	}
312 319
 

+ 25
- 8
page.go View File

@@ -124,6 +124,11 @@ const packageTemplateString = `<!DOCTYPE html>
124 124
 						</div>
125 125
 					</div>
126 126
 				</div>
127
+				{{ if .Repo.MajorVersion.Unstable }}
128
+					<div class="col-sm-12 alert alert-danger">
129
+						This is an <b><i>unstable</i></b> package and should <i>not</i> be used in released code.
130
+					</div>
131
+				{{ end }}
127 132
 				<div class="row" >
128 133
 					<div class="col-sm-12" >
129 134
 						<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>
153 158
 						{{ if .LatestVersions }}
154 159
 							{{ range .LatestVersions }}
155 160
 								<div>
156
-									<a href="//{{gopkgVersionRoot $.Repo .}}{{$.Repo.SubPath}}" {{if eq .Major $.Repo.MajorVersion.Major}}class="current"{{end}} >v{{.Major}}</a>
161
+									<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>
157 162
 									&rarr;
158 163
 									<span class="label label-default">{{.}}</span>
159 164
 								</div>
@@ -227,21 +232,33 @@ func renderPackagePage(resp http.ResponseWriter, req *http.Request, repo *Repo)
227 232
 		Repo: repo,
228 233
 	}
229 234
 
230
-	// calculate version mapping
231
-	latestVersionsMap := make(map[int]Version)
235
+	// Calculate the latest version for each major version, both stable and unstable.
236
+	latestVersions := make(map[int]Version)
237
+	latestUnstable := make(map[int]Version)
232 238
 	for _, v := range repo.AllVersions {
233
-		v2, exists := latestVersionsMap[v.Major]
239
+		m := latestVersions
240
+		if v.Unstable {
241
+			m = latestUnstable
242
+		}
243
+		v2, exists := m[v.Major]
234 244
 		if !exists || v2.Less(v) {
235
-			latestVersionsMap[v.Major] = v
245
+			m[v.Major] = v
236 246
 		}
237 247
 	}
238
-	data.FullVersion = latestVersionsMap[repo.MajorVersion.Major]
239
-	data.LatestVersions = make(VersionList, 0, len(latestVersionsMap))
240
-	for _, v := range latestVersionsMap {
248
+	data.LatestVersions = make(VersionList, 0, len(latestVersions))
249
+	for _, v := range latestVersions {
241 250
 		data.LatestVersions = append(data.LatestVersions, v)
242 251
 	}
243 252
 	sort.Sort(sort.Reverse(data.LatestVersions))
244 253
 
254
+	if repo.MajorVersion.Unstable {
255
+		data.FullVersion = latestUnstable[repo.MajorVersion.Major]
256
+		// Prepend post-sorting so it's show first.
257
+		data.LatestVersions = append([]Version{data.FullVersion}, data.LatestVersions...)
258
+	} else {
259
+		data.FullVersion = latestVersions[repo.MajorVersion.Major]
260
+	}
261
+
245 262
 	var dataMutex sync.Mutex
246 263
 	wantResps := 2
247 264
 	gotResp := make(chan bool, wantResps)

+ 66
- 44
version.go View File

@@ -7,20 +7,29 @@ import (
7 7
 // Version represents a version number.
8 8
 // An element that is not present is represented as -1.
9 9
 type Version struct {
10
-	Major, Minor, Patch int
10
+	Major    int
11
+	Minor    int
12
+	Patch    int
13
+	Unstable bool
11 14
 }
12 15
 
16
+const unstableSuffix = "-unstable"
17
+
13 18
 func (v Version) String() string {
14 19
 	if v.Major < 0 {
15 20
 		panic(fmt.Sprintf("cannot stringify invalid version (major is %d)", v.Major))
16 21
 	}
22
+	suffix := ""
23
+	if v.Unstable {
24
+		suffix = unstableSuffix
25
+	}
17 26
 	if v.Minor < 0 {
18
-		return fmt.Sprintf("v%d", v.Major)
27
+		return fmt.Sprintf("v%d%s", v.Major, suffix)
19 28
 	}
20 29
 	if v.Patch < 0 {
21
-		return fmt.Sprintf("v%d.%d", v.Major, v.Minor)
30
+		return fmt.Sprintf("v%d.%d%s", v.Major, v.Minor, suffix)
22 31
 	}
23
-	return fmt.Sprintf("v%d.%d.%d", v.Major, v.Minor, v.Patch)
32
+	return fmt.Sprintf("v%d.%d.%d%s", v.Major, v.Minor, v.Patch, suffix)
24 33
 }
25 34
 
26 35
 // Less returns whether v is less than other.
@@ -31,7 +40,10 @@ func (v Version) Less(other Version) bool {
31 40
 	if v.Minor != other.Minor {
32 41
 		return v.Minor < other.Minor
33 42
 	}
34
-	return v.Patch < other.Patch
43
+	if v.Patch != other.Patch {
44
+		return v.Patch < other.Patch
45
+	}
46
+	return v.Unstable && !other.Unstable
35 47
 }
36 48
 
37 49
 // Contains returns whether version v contains version other.
@@ -40,7 +52,13 @@ func (v Version) Less(other Version) bool {
40 52
 //
41 53
 // For example, Version{1, 1, -1} contains both Version{1, 1, -1} and Version{1, 1, 2},
42 54
 // but not Version{1, -1, -1} or Version{1, 2, -1}.
55
+//
56
+// Unstable versions (-unstable) only contain unstable versions, and stable
57
+// versions only contain stable versions.
43 58
 func (v Version) Contains(other Version) bool {
59
+	if v.Unstable != other.Unstable {
60
+		return false
61
+	}
44 62
 	if v.Patch != -1 {
45 63
 		return v == other
46 64
 	}
@@ -55,61 +73,65 @@ func (v Version) IsValid() bool {
55 73
 }
56 74
 
57 75
 // InvalidVersion represents a version that can't be parsed.
58
-var InvalidVersion = Version{-1, -1, -1}
76
+var InvalidVersion = Version{-1, -1, -1, false}
59 77
 
60
-func parseVersion(s string) (Version, bool) {
78
+func parseVersion(s string) (v Version, ok bool) {
79
+	v = InvalidVersion
61 80
 	if len(s) < 2 {
62
-		return InvalidVersion, false
81
+		return
63 82
 	}
64 83
 	if s[0] != 'v' {
65
-		return InvalidVersion, false
84
+		return
66 85
 	}
67
-	v := Version{-1, -1, -1}
86
+	vout := InvalidVersion
87
+	unstable := false
68 88
 	i := 1
69
-	v.Major, i = parseVersionPart(s, i)
70
-	if i < 0 {
71
-		return InvalidVersion, false
72
-	}
73
-	if i == len(s) {
74
-		return v, true
75
-	}
76
-	v.Minor, i = parseVersionPart(s, i)
77
-	if i < 0 {
78
-		return InvalidVersion, false
79
-	}
80
-	if i == len(s) {
81
-		return v, true
82
-	}
83
-	v.Patch, i = parseVersionPart(s, i)
84
-	if i < 0 || i < len(s) {
85
-		return InvalidVersion, false
89
+	for _, vptr := range []*int{&vout.Major, &vout.Minor, &vout.Patch} {
90
+		*vptr, unstable, i = parseVersionPart(s, i)
91
+		if i < 0 {
92
+			return
93
+		}
94
+		if i == len(s) {
95
+			vout.Unstable = unstable
96
+			return vout, true
97
+		}
86 98
 	}
87
-	return v, true
99
+	return
88 100
 }
89 101
 
90
-func parseVersionPart(s string, i int) (part int, newi int) {
91
-	dot := i
92
-	for dot < len(s) && s[dot] != '.' {
93
-		dot++
94
-	}
95
-	if dot == i || dot-i > 1 && s[i] == '0' {
96
-		return -1, -1
97
-	}
98
-	for i < len(s) {
99
-		if s[i] < '0' || s[i] > '9' {
100
-			return -1, -1
102
+func parseVersionPart(s string, i int) (part int, unstable bool, newi int) {
103
+	j := i
104
+	for j < len(s) && s[j] != '.' && s[j] != '-' {
105
+		j++
106
+	}
107
+	if j == i || j-i > 1 && s[i] == '0' {
108
+		return -1, false, -1
109
+	}
110
+	c := s[i]
111
+	for {
112
+		if c < '0' || c > '9' {
113
+			return -1, false, -1
101 114
 		}
102 115
 		part *= 10
103
-		part += int(s[i] - '0')
116
+		part += int(c - '0')
104 117
 		if part < 0 {
105
-			return -1, -1
118
+			return -1, false, -1
106 119
 		}
107 120
 		i++
108
-		if i+1 < len(s) && s[i] == '.' {
109
-			return part, i + 1
121
+		if i == len(s) {
122
+			return part, false, i
123
+		}
124
+		c = s[i]
125
+		if i+1 < len(s) {
126
+			if c == '.' {
127
+				return part, false, i + 1
128
+			}
129
+			if c == '-' && s[i:] == unstableSuffix {
130
+				return part, true, i + len(unstableSuffix)
131
+			}
110 132
 		}
111 133
 	}
112
-	return part, i
134
+	panic("unreachable")
113 135
 }
114 136
 
115 137
 // VersionList implements sort.Interface

+ 59
- 46
version_test.go View File

@@ -13,31 +13,35 @@ var _ = Suite(&VersionSuite{})
13 13
 type VersionSuite struct{}
14 14
 
15 15
 var versionParseTests = []struct {
16
-	major, minor, patch int
17
-	s                   string
16
+	major int
17
+	minor int
18
+	patch int
19
+	dev   bool
20
+	s     string
18 21
 }{
19
-	{-1, -1, -1, "v"},
20
-	{-1, -1, -1, "v-1"},
21
-	{-1, -1, -1, "v01"},
22
-	{-1, -1, -1, "v1.01"},
23
-	{-1, -1, -1, "a1"},
24
-	{-1, -1, -1, "v1a"},
25
-	{-1, -1, -1, "v1..2"},
26
-	{-1, -1, -1, "v1.2.3.4"},
27
-	{-1, -1, -1, "v1."},
28
-	{-1, -1, -1, "v1.2."},
29
-	{-1, -1, -1, "v1.2.3."},
22
+	{-1, -1, -1, false, "v"},
23
+	{-1, -1, -1, false, "v-1"},
24
+	{-1, -1, -1, false, "v-deb"},
25
+	{-1, -1, -1, false, "v01"},
26
+	{-1, -1, -1, false, "v1.01"},
27
+	{-1, -1, -1, false, "a1"},
28
+	{-1, -1, -1, false, "v1a"},
29
+	{-1, -1, -1, false, "v1..2"},
30
+	{-1, -1, -1, false, "v1.2.3.4"},
31
+	{-1, -1, -1, false, "v1."},
32
+	{-1, -1, -1, false, "v1.2."},
33
+	{-1, -1, -1, false, "v1.2.3."},
30 34
 
31
-	{0, -1, -1,
32
-		"v0"},
33
-	{1, -1, -1,
34
-		"v1"},
35
-	{1, 2, -1,
36
-		"v1.2"},
37
-	{1, 2, 3,
38
-		"v1.2.3"},
39
-	{12, 34, 56,
40
-		"v12.34.56"},
35
+	{0, -1, -1, false, "v0"},
36
+	{0, -1, -1, true, "v0-unstable"},
37
+	{1, -1, -1, false, "v1"},
38
+	{1, -1, -1, true, "v1-unstable"},
39
+	{1, 2, -1, false, "v1.2"},
40
+	{1, 2, -1, true, "v1.2-unstable"},
41
+	{1, 2, 3, false, "v1.2.3"},
42
+	{1, 2, 3, true, "v1.2.3-unstable"},
43
+	{12, 34, 56, false, "v12.34.56"},
44
+	{12, 34, 56, true, "v12.34.56-unstable"},
41 45
 }
42 46
 
43 47
 func (s *VersionSuite) TestParse(c *C) {
@@ -48,7 +52,7 @@ func (s *VersionSuite) TestParse(c *C) {
48 52
 				c.Fatalf("version %q is invalid but parsed as %#v", t.s, got)
49 53
 			}
50 54
 		} else {
51
-			want := Version{t.major, t.minor, t.patch}
55
+			want := Version{t.major, t.minor, t.patch, t.dev}
52 56
 			if got != want {
53 57
 				c.Fatalf("version %q must parse as %#v, got %#v", t.s, want, got)
54 58
 			}
@@ -61,24 +65,29 @@ func (s *VersionSuite) TestParse(c *C) {
61 65
 
62 66
 var versionLessTests = []struct {
63 67
 	oneMajor, oneMinor, onePatch int
68
+	oneUnstable                  bool
64 69
 	twoMajor, twoMinor, twoPatch int
65
-	less                         bool
70
+	twoUnstable, less            bool
66 71
 }{
67
-	{0, 0, 0, 0, 0, 0, false},
68
-	{1, 0, 0, 1, 0, 0, false},
69
-	{1, 0, 0, 1, 1, 0, true},
70
-	{1, 0, 0, 2, 0, 0, true},
71
-	{0, 1, 0, 0, 1, 0, false},
72
-	{0, 1, 0, 0, 1, 1, true},
73
-	{0, 0, 0, 0, 2, 0, true},
74
-	{0, 0, 1, 0, 0, 1, false},
75
-	{0, 0, 1, 0, 0, 2, true},
72
+	{0, 0, 0, false, 0, 0, 0, false, false},
73
+	{1, 0, 0, false, 1, 0, 0, false, false},
74
+	{1, 0, 0, false, 1, 1, 0, false, true},
75
+	{1, 0, 0, false, 2, 0, 0, false, true},
76
+	{0, 1, 0, false, 0, 1, 0, false, false},
77
+	{0, 1, 0, false, 0, 1, 1, false, true},
78
+	{0, 0, 0, false, 0, 2, 0, false, true},
79
+	{0, 0, 1, false, 0, 0, 1, false, false},
80
+	{0, 0, 1, false, 0, 0, 2, false, true},
81
+
82
+	{0, 0, 0, false, 0, 0, 0, true, false},
83
+	{0, 0, 0, true, 0, 0, 0, false, true},
84
+	{0, 0, 1, true, 0, 0, 0, false, false},
76 85
 }
77 86
 
78 87
 func (s *VersionSuite) TestLess(c *C) {
79 88
 	for _, t := range versionLessTests {
80
-		one := Version{t.oneMajor, t.oneMinor, t.onePatch}
81
-		two := Version{t.twoMajor, t.twoMinor, t.twoPatch}
89
+		one := Version{t.oneMajor, t.oneMinor, t.onePatch, t.oneUnstable}
90
+		two := Version{t.twoMajor, t.twoMinor, t.twoPatch, t.twoUnstable}
82 91
 		if one.Less(two) != t.less {
83 92
 			c.Fatalf("version %s < %s returned %v", one, two, !t.less)
84 93
 		}
@@ -87,21 +96,25 @@ func (s *VersionSuite) TestLess(c *C) {
87 96
 
88 97
 var versionContainsTests = []struct {
89 98
 	oneMajor, oneMinor, onePatch int
99
+	oneUnstable                  bool
90 100
 	twoMajor, twoMinor, twoPatch int
91
-	contains                     bool
101
+	twoUnstable, contains        bool
92 102
 }{
93
-	{12, 34, 56, 12, 34, 56, true},
94
-	{12, 34, 56, 12, 34, 78, false},
95
-	{12, 34, -1, 12, 34, 56, true},
96
-	{12, 34, -1, 12, 78, 56, false},
97
-	{12, -1, -1, 12, 34, 56, true},
98
-	{12, -1, -1, 78, 34, 56, false},
103
+	{12, 34, 56, false, 12, 34, 56, false, true},
104
+	{12, 34, 56, false, 12, 34, 78, false, false},
105
+	{12, 34, -1, false, 12, 34, 56, false, true},
106
+	{12, 34, -1, false, 12, 78, 56, false, false},
107
+	{12, -1, -1, false, 12, 34, 56, false, true},
108
+	{12, -1, -1, false, 78, 34, 56, false, false},
109
+
110
+	{12, -1, -1, true, 12, -1, -1, false, false},
111
+	{12, -1, -1, false, 12, -1, -1, true, false},
99 112
 }
100 113
 
101 114
 func (s *VersionSuite) TestContains(c *C) {
102 115
 	for _, t := range versionContainsTests {
103
-		one := Version{t.oneMajor, t.oneMinor, t.onePatch}
104
-		two := Version{t.twoMajor, t.twoMinor, t.twoPatch}
116
+		one := Version{t.oneMajor, t.oneMinor, t.onePatch, t.oneUnstable}
117
+		two := Version{t.twoMajor, t.twoMinor, t.twoPatch, t.twoUnstable}
105 118
 		if one.Contains(two) != t.contains {
106 119
 			c.Fatalf("version %s.Contains(%s) returned %v", one, two, !t.contains)
107 120
 		}
@@ -110,5 +123,5 @@ func (s *VersionSuite) TestContains(c *C) {
110 123
 
111 124
 func (s *VersionSuite) TestIsValid(c *C) {
112 125
 	c.Assert(InvalidVersion.IsValid(), Equals, false)
113
-	c.Assert(Version{0, 0, 0}.IsValid(), Equals, true)
126
+	c.Assert(Version{0, 0, 0, false}.IsValid(), Equals, true)
114 127
 }