blob: 9ae409b27a780c9173e3b90645b6de8a09d72aaa [file] [log] [blame]
// Copyright 2018 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 bitbucket
import (
"fmt"
"io"
"net/http"
"net/url"
"strings"
"time"
"cmd/go/internal/modfetch/codehost"
web "cmd/go/internal/web2"
)
func Lookup(path string) (codehost.Repo, error) {
f := strings.Split(path, "/")
if len(f) < 3 || f[0] != "bitbucket.org" {
return nil, fmt.Errorf("bitbucket repo must be bitbucket.org/org/project")
}
// Check for moved, renamed, or incorrect case in repository.
// Otherwise the API calls all appear to work.
// We need to do better here, but it's unclear exactly what.
var data struct {
FullName string `json:"full_name"`
}
err := web.Get("https://api.bitbucket.org/2.0/repositories/"+url.PathEscape(f[1])+"/"+url.PathEscape(f[2]), web.DecodeJSON(&data))
if err != nil {
return nil, err
}
myFullName := f[1] + "/" + f[2]
if myFullName != data.FullName {
why := "moved"
if strings.EqualFold(myFullName, data.FullName) {
why = "wrong case"
}
return nil, fmt.Errorf("module path of repo is bitbucket.org/%s, not %s (%s)", data.FullName, path, why)
}
return newRepo(f[1], f[2]), nil
}
func newRepo(owner, repository string) codehost.Repo {
return &repo{owner: owner, repo: repository}
}
type repo struct {
owner string
repo string
}
func (r *repo) Root() string {
return "bitbucket.org/" + r.owner + "/" + r.repo
}
func (r *repo) Tags(prefix string) ([]string, error) {
var tags []string
u := "https://api.bitbucket.org/2.0/repositories/" + url.PathEscape(r.owner) + "/" + url.PathEscape(r.repo) + "/refs/tags"
var data struct {
Values []struct {
Name string `json:"name"`
} `json:"values"`
}
var hdr http.Header
err := web.Get(u, web.Header(&hdr), web.DecodeJSON(&data))
if err != nil {
return nil, err
}
for _, t := range data.Values {
if strings.HasPrefix(t.Name, prefix) {
tags = append(tags, t.Name)
}
}
return tags, nil
}
func (r *repo) LatestAt(t time.Time, branch string) (*codehost.RevInfo, error) {
u := "https://api.bitbucket.org/2.0/repositories/" + url.PathEscape(r.owner) + "/" + url.PathEscape(r.repo) + "/commits/" + url.QueryEscape(branch) + "?pagelen=10"
for u != "" {
var commits struct {
Values []struct {
Hash string `json:"hash"`
Date string `json:"date"`
} `json:"values"`
Next string `json:"next"`
}
err := web.Get(u, web.DecodeJSON(&commits))
if err != nil {
return nil, err
}
if len(commits.Values) == 0 {
return nil, fmt.Errorf("no commits")
}
for _, commit := range commits.Values {
d, err := time.Parse(time.RFC3339, commit.Date)
if err != nil {
return nil, err
}
if d.Before(t) {
info := &codehost.RevInfo{
Name: commit.Hash,
Short: codehost.ShortenSHA1(commit.Hash),
Time: d,
}
return info, nil
}
}
u = commits.Next
}
return nil, fmt.Errorf("no commits")
}
func (r *repo) Stat(rev string) (*codehost.RevInfo, error) {
var tag string
if !codehost.AllHex(rev) {
tag = rev
}
var commit struct {
Hash string `json:"hash"`
Type string `json:"type"`
Date string `json:"date"`
}
err := web.Get(
"https://api.bitbucket.org/2.0/repositories/"+url.PathEscape(r.owner)+"/"+url.PathEscape(r.repo)+"/commit/"+rev,
web.DecodeJSON(&commit),
)
if err != nil {
return nil, err
}
rev = commit.Hash
if rev == "" {
return nil, fmt.Errorf("no commits")
}
d, err := time.Parse(time.RFC3339, commit.Date)
if err != nil {
return nil, err
}
info := &codehost.RevInfo{
Name: rev,
Short: codehost.ShortenSHA1(rev),
Version: tag,
Time: d,
}
return info, nil
}
func (r *repo) ReadFile(rev, file string, maxSize int64) ([]byte, error) {
// TODO: Use maxSize.
// TODO: I could not find an API endpoint for getting information about an
// individual file, and I do not know if the raw file download endpoint is
// a stable API.
var body []byte
err := web.Get(
"https://bitbucket.org/"+url.PathEscape(r.owner)+"/"+url.PathEscape(r.repo)+"/raw/"+url.PathEscape(rev)+"/"+url.PathEscape(file),
web.ReadAllBody(&body),
)
return body, err
}
func (r *repo) ReadZip(rev, subdir string, maxSize int64) (zip io.ReadCloser, actualSubdir string, err error) {
// TODO: Make web.Get copy to file for us, with limit.
var body io.ReadCloser
err = web.Get(
"https://bitbucket.org/"+url.PathEscape(r.owner)+"/"+url.PathEscape(r.repo)+"/get/"+url.PathEscape(rev)+".zip",
web.Body(&body),
)
if err != nil {
if body != nil {
body.Close()
}
return nil, "", err
}
return body, "", nil
}