Browse Source

Improved version matching.

master
Gustavo Niemeyer 10 years ago
parent
commit
e688f13d79
1 changed files with 91 additions and 26 deletions
  1. +91
    -26
      main.go

+ 91
- 26
main.go View File

@@ -74,12 +74,17 @@ type Repo struct {
HubRoot string HubRoot string
PkgRoot string PkgRoot string
PkgPath string PkgPath string
Version string
Version Version
} }


var re = regexp.MustCompile(`^/([a-z0-9][-a-z0-9]+/)?(v0|v[1-9][0-9]*)/([a-z][-a-z0-9]*)(?:\.git)?((?:/[a-z][-a-z0-9]*)*)$`)
var re = 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]*)*)$`)


func handler(resp http.ResponseWriter, req *http.Request) { func handler(resp http.ResponseWriter, req *http.Request) {
if req.URL.Path == "/health-check" {
resp.Write([]byte("ok"))
return
}

log.Printf("%s requested %s", req.RemoteAddr, req.URL) log.Printf("%s requested %s", req.RemoteAddr, req.URL)


if req.URL.Path == "/" { if req.URL.Path == "/" {
@@ -90,7 +95,13 @@ func handler(resp http.ResponseWriter, req *http.Request) {


m := re.FindStringSubmatch(req.URL.Path) m := re.FindStringSubmatch(req.URL.Path)
if m == nil { if m == nil {
resp.WriteHeader(404)
sendNotFound(resp, "Unsupported URL pattern; see the documentation at gopkg.in for details.")
return
}

if strings.Contains(m[2], ".") {
sendNotFound(resp, "Import paths take the major version only (/%s/ instead of /%s/); see docs at gopkg.in for the reasoning.",
m[2][:strings.Index(m[2], ".")], m[2])
return return
} }


@@ -100,33 +111,52 @@ func handler(resp http.ResponseWriter, req *http.Request) {
GitRoot: "https://github.com/go-" + m[3] + "/" + m[3], GitRoot: "https://github.com/go-" + m[3] + "/" + m[3],
PkgRoot: "gopkg.in/" + m[2] + "/" + m[3], PkgRoot: "gopkg.in/" + m[2] + "/" + m[3],
PkgPath: "gopkg.in/" + m[2] + "/" + m[3] + m[4], PkgPath: "gopkg.in/" + m[2] + "/" + m[3] + m[4],
Version: m[2],
} }
} else { } else {
repo = &Repo{ repo = &Repo{
GitRoot: "https://github.com/" + m[1] + m[3], GitRoot: "https://github.com/" + m[1] + m[3],
PkgRoot: "gopkg.in/" + m[1] + m[2] + "/" + m[3], PkgRoot: "gopkg.in/" + m[1] + m[2] + "/" + m[3],
PkgPath: "gopkg.in/" + m[1] + m[2] + "/" + m[3] + m[4], PkgPath: "gopkg.in/" + m[1] + m[2] + "/" + m[3] + m[4],
Version: m[2],
} }
} }


var ok bool
repo.Version, ok = parseVersion(m[2])
if !ok {
sendNotFound(resp, "Version %q improperly considered invalid; please warn the service maintainers.", m[2])
}

repo.HubRoot = repo.GitRoot repo.HubRoot = repo.GitRoot


// Run this concurrently to avoid waiting later.
nameVersioned := nameHasVersion(repo)

refs, err := hackedRefs(repo) refs, err := hackedRefs(repo)
switch err { switch err {
case nil: case nil:
repo.GitRoot = "https://" + repo.PkgRoot repo.GitRoot = "https://" + repo.PkgRoot
case ErrNoRepo: case ErrNoRepo:
repo.GitRoot += "-" + repo.Version
repo.HubRoot += "-" + repo.Version
if <-nameVersioned {
v := repo.Version.String()
repo.GitRoot += "-" + v
repo.HubRoot += "-" + v
break
}
sendNotFound(resp, "GitHub repository not found at %s or %s-%s", repo.HubRoot, repo.HubRoot, repo.Version)
return
case ErrNoVersion: case ErrNoVersion:
log.Print(err)
resp.WriteHeader(http.StatusNotFound)
v := repo.Version.String()
if repo.Version.Minor == -1 {
sendNotFound(resp, `GitHub repository at %s has no branch or tag "%s", "%s.N" or "%s.N.M"`, repo.HubRoot, v, v, v)
} else if repo.Version.Patch == -1 {
sendNotFound(resp, `GitHub repository at %s has no branch or tag "%s" or "%s.N"`, repo.HubRoot, v, v)
} else {
sendNotFound(resp, `GitHub repository at %s has no branch or tag "%s"`, repo.HubRoot, v)
}
return return
default: default:
log.Print(err)
resp.WriteHeader(http.StatusNotFound)
resp.WriteHeader(http.StatusBadGateway)
resp.Write([]byte(fmt.Sprintf("Cannot obtain refs from GitHub: %v", err)))
return return
} }


@@ -146,13 +176,40 @@ func handler(resp http.ResponseWriter, req *http.Request) {
tmpl.Execute(resp, repo) tmpl.Execute(resp, repo)
} }


func sendNotFound(resp http.ResponseWriter, msg string, args ...interface{}) {
if len(args) > 0 {
msg = fmt.Sprintf(msg, args...)
}
resp.WriteHeader(http.StatusNotFound)
resp.Write([]byte(msg))
}

// TODO Timeouts for these http interactions. Use the new support coming in 1.3.

const refsSuffix = ".git/info/refs?service=git-upload-pack"

func nameHasVersion(repo *Repo) chan bool {
ch := make(chan bool, 1)
go func() {
resp, err := http.Head(repo.HubRoot + "-" + repo.Version.String() + refsSuffix)
if err != nil {
ch <- false
}
if resp.Body != nil {
resp.Body.Close()
}
ch <- resp.StatusCode == 200
}()
return ch
}

var ErrNoRepo = errors.New("repository not found in github") var ErrNoRepo = errors.New("repository not found in github")
var ErrNoVersion = errors.New("version reference not found in github") var ErrNoVersion = errors.New("version reference not found in github")


func hackedRefs(repo *Repo) (data []byte, err error) { func hackedRefs(repo *Repo) (data []byte, err error) {
resp, err := http.Get(repo.HubRoot + ".git/info/refs?service=git-upload-pack")
resp, err := http.Get(repo.HubRoot + refsSuffix)
if err != nil { if err != nil {
return nil, fmt.Errorf("cannot talk to github: %v", err)
return nil, fmt.Errorf("cannot talk to GitHub: %v", err)
} }
switch resp.StatusCode { switch resp.StatusCode {
case 200: case 200:
@@ -160,18 +217,18 @@ func hackedRefs(repo *Repo) (data []byte, err error) {
case 401, 404: case 401, 404:
return nil, ErrNoRepo return nil, ErrNoRepo
default: default:
return nil, fmt.Errorf("error from github: %v", resp.Status)
return nil, fmt.Errorf("error from GitHub: %v", resp.Status)
} }


data, err = ioutil.ReadAll(resp.Body) data, err = ioutil.ReadAll(resp.Body)
if err != nil { if err != nil {
return nil, fmt.Errorf("error reading from github: %v", err)
return nil, fmt.Errorf("error reading from GitHub: %v", err)
} }


vhead := "refs/heads/" + repo.Version
vtag := "refs/tags/" + repo.Version
var mrefi, mrefj, vrefi, vrefj int
var mrefi, mrefj int
var vrefi, vrefj int
var vrefv = InvalidVersion
var unversioned = true


sdata := string(data) sdata := string(data)
for i, j := 0, 0; i < len(data); i = j { for i, j := 0, 0; i < len(data); i = j {
@@ -184,7 +241,7 @@ func hackedRefs(repo *Repo) (data []byte, err error) {
} }
j = i + int(size) j = i + int(size)
if j > len(sdata) { if j > len(sdata) {
return nil, fmt.Errorf("incomplete refs data received from github")
return nil, fmt.Errorf("incomplete refs data received from GitHub")
} }
if sdata[0] == '#' { if sdata[0] == '#' {
continue continue
@@ -212,14 +269,22 @@ func hackedRefs(repo *Repo) (data []byte, err error) {
mrefj = hashj mrefj = hashj
} }


if name == vtag || name == vhead {
vrefi = hashi
vrefj = hashj
if strings.HasPrefix(name, "refs/heads/v") || strings.HasPrefix(name, "refs/tags/v") {
v, ok := parseVersion(name[strings.IndexByte(name, 'v'):])
if ok && repo.Version.Contains(v) && (!vrefv.IsValid() || vrefv.Less(v)) {
vrefv = v
vrefi = hashi
vrefj = hashj
}
if ok {
unversioned = false
}
} }
}


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


if mrefi == 0 || vrefi == 0 { if mrefi == 0 || vrefi == 0 {


Loading…
Cancel
Save