// Copyright 2013 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 or at // https://developers.google.com/open-source/licenses/bsd. package gosrc import ( "log" "net/http" "path" "regexp" "time" ) func init() { addService(&service{ pattern: regexp.MustCompile(`^bitbucket\.org/(?P[a-z0-9A-Z_.\-]+)/(?P[a-z0-9A-Z_.\-]+)(?P/[a-z0-9A-Z_.\-/]*)?$`), prefix: "bitbucket.org/", get: getBitbucketDir, }) } var bitbucketEtagRe = regexp.MustCompile(`^(hg|git)-`) type bitbucketRepo struct { Scm string CreatedOn string `json:"created_on"` LastUpdated string `json:"last_updated"` ForkOf struct { Scm string } `json:"fork_of"` Followers int `json:"followers_count"` IsFork bool `json:"is_fork"` } type bitbucketNode struct { Node string `json:"node"` Timestamp string `json:"utctimestamp"` } func getBitbucketDir(client *http.Client, match map[string]string, savedEtag string) (*Directory, error) { var repo *bitbucketRepo c := &httpClient{client: client} if m := bitbucketEtagRe.FindStringSubmatch(savedEtag); m != nil { match["vcs"] = m[1] } else { repo, err := getBitbucketRepo(c, match) if err != nil { return nil, err } match["vcs"] = repo.Scm } tags := make(map[string]string) timestamps := make(map[string]time.Time) for _, nodeType := range []string{"branches", "tags"} { var nodes map[string]bitbucketNode if _, err := c.getJSON(expand("https://api.bitbucket.org/1.0/repositories/{owner}/{repo}/{0}", match, nodeType), &nodes); err != nil { return nil, err } for t, n := range nodes { tags[t] = n.Node const timeFormat = "2006-01-02 15:04:05Z07:00" committed, err := time.Parse(timeFormat, n.Timestamp) if err != nil { log.Println("error parsing timestamp:", n.Timestamp) continue } timestamps[t] = committed } } var err error tag, commit, err := bestTag(tags, defaultTags[match["vcs"]]) if err != nil { return nil, err } match["tag"] = tag match["commit"] = commit etag := expand("{vcs}-{commit}", match) if etag == savedEtag { return nil, NotModifiedError{Since: timestamps[tag]} } if repo == nil { repo, err = getBitbucketRepo(c, match) if err != nil { return nil, err } } var contents struct { Directories []string Files []struct { Path string } } if _, err := c.getJSON(expand("https://api.bitbucket.org/1.0/repositories/{owner}/{repo}/src/{tag}{dir}/", match), &contents); err != nil { return nil, err } var files []*File var dataURLs []string for _, f := range contents.Files { _, name := path.Split(f.Path) if isDocFile(name) { files = append(files, &File{Name: name, BrowseURL: expand("https://bitbucket.org/{owner}/{repo}/src/{tag}/{0}", match, f.Path)}) dataURLs = append(dataURLs, expand("https://api.bitbucket.org/1.0/repositories/{owner}/{repo}/raw/{tag}/{0}", match, f.Path)) } } if err := c.getFiles(dataURLs, files); err != nil { return nil, err } status := Active if isBitbucketDeadEndFork(repo) { status = DeadEndFork } return &Directory{ BrowseURL: expand("https://bitbucket.org/{owner}/{repo}/src/{tag}{dir}", match), Etag: etag, Files: files, LineFmt: "%s#cl-%d", ProjectName: match["repo"], ProjectRoot: expand("bitbucket.org/{owner}/{repo}", match), ProjectURL: expand("https://bitbucket.org/{owner}/{repo}/", match), Subdirectories: contents.Directories, VCS: match["vcs"], Status: status, Fork: repo.IsFork, Stars: repo.Followers, }, nil } func getBitbucketRepo(c *httpClient, match map[string]string) (*bitbucketRepo, error) { var repo bitbucketRepo if _, err := c.getJSON(expand("https://api.bitbucket.org/1.0/repositories/{owner}/{repo}", match), &repo); err != nil { return nil, err } return &repo, nil } func isBitbucketDeadEndFork(repo *bitbucketRepo) bool { l := "2006-01-02T15:04:05.999999999" created, err := time.Parse(l, repo.CreatedOn) if err != nil { return false } updated, err := time.Parse(l, repo.LastUpdated) if err != nil { return false } isDeadEndFork := false if repo.ForkOf.Scm != "" && created.Unix() >= updated.Unix() { isDeadEndFork = true } return isDeadEndFork }