You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

172 lines
4.3 KiB

  1. // Copyright 2013 The Go Authors. All rights reserved.
  2. //
  3. // Use of this source code is governed by a BSD-style
  4. // license that can be found in the LICENSE file or at
  5. // https://developers.google.com/open-source/licenses/bsd.
  6. package gosrc
  7. import (
  8. "log"
  9. "net/http"
  10. "path"
  11. "regexp"
  12. "time"
  13. )
  14. func init() {
  15. addService(&service{
  16. pattern: regexp.MustCompile(`^bitbucket\.org/(?P<owner>[a-z0-9A-Z_.\-]+)/(?P<repo>[a-z0-9A-Z_.\-]+)(?P<dir>/[a-z0-9A-Z_.\-/]*)?$`),
  17. prefix: "bitbucket.org/",
  18. get: getBitbucketDir,
  19. })
  20. }
  21. var bitbucketEtagRe = regexp.MustCompile(`^(hg|git)-`)
  22. type bitbucketRepo struct {
  23. Scm string
  24. CreatedOn string `json:"created_on"`
  25. LastUpdated string `json:"last_updated"`
  26. ForkOf struct {
  27. Scm string
  28. } `json:"fork_of"`
  29. Followers int `json:"followers_count"`
  30. IsFork bool `json:"is_fork"`
  31. }
  32. type bitbucketNode struct {
  33. Node string `json:"node"`
  34. Timestamp string `json:"utctimestamp"`
  35. }
  36. func getBitbucketDir(client *http.Client, match map[string]string, savedEtag string) (*Directory, error) {
  37. var repo *bitbucketRepo
  38. c := &httpClient{client: client}
  39. if m := bitbucketEtagRe.FindStringSubmatch(savedEtag); m != nil {
  40. match["vcs"] = m[1]
  41. } else {
  42. repo, err := getBitbucketRepo(c, match)
  43. if err != nil {
  44. return nil, err
  45. }
  46. match["vcs"] = repo.Scm
  47. }
  48. tags := make(map[string]string)
  49. timestamps := make(map[string]time.Time)
  50. for _, nodeType := range []string{"branches", "tags"} {
  51. var nodes map[string]bitbucketNode
  52. if _, err := c.getJSON(expand("https://api.bitbucket.org/1.0/repositories/{owner}/{repo}/{0}", match, nodeType), &nodes); err != nil {
  53. return nil, err
  54. }
  55. for t, n := range nodes {
  56. tags[t] = n.Node
  57. const timeFormat = "2006-01-02 15:04:05Z07:00"
  58. committed, err := time.Parse(timeFormat, n.Timestamp)
  59. if err != nil {
  60. log.Println("error parsing timestamp:", n.Timestamp)
  61. continue
  62. }
  63. timestamps[t] = committed
  64. }
  65. }
  66. var err error
  67. tag, commit, err := bestTag(tags, defaultTags[match["vcs"]])
  68. if err != nil {
  69. return nil, err
  70. }
  71. match["tag"] = tag
  72. match["commit"] = commit
  73. etag := expand("{vcs}-{commit}", match)
  74. if etag == savedEtag {
  75. return nil, NotModifiedError{Since: timestamps[tag]}
  76. }
  77. if repo == nil {
  78. repo, err = getBitbucketRepo(c, match)
  79. if err != nil {
  80. return nil, err
  81. }
  82. }
  83. var contents struct {
  84. Directories []string
  85. Files []struct {
  86. Path string
  87. }
  88. }
  89. if _, err := c.getJSON(expand("https://api.bitbucket.org/1.0/repositories/{owner}/{repo}/src/{tag}{dir}/", match), &contents); err != nil {
  90. return nil, err
  91. }
  92. var files []*File
  93. var dataURLs []string
  94. for _, f := range contents.Files {
  95. _, name := path.Split(f.Path)
  96. if isDocFile(name) {
  97. files = append(files, &File{Name: name, BrowseURL: expand("https://bitbucket.org/{owner}/{repo}/src/{tag}/{0}", match, f.Path)})
  98. dataURLs = append(dataURLs, expand("https://api.bitbucket.org/1.0/repositories/{owner}/{repo}/raw/{tag}/{0}", match, f.Path))
  99. }
  100. }
  101. if err := c.getFiles(dataURLs, files); err != nil {
  102. return nil, err
  103. }
  104. status := Active
  105. if isBitbucketDeadEndFork(repo) {
  106. status = DeadEndFork
  107. }
  108. return &Directory{
  109. BrowseURL: expand("https://bitbucket.org/{owner}/{repo}/src/{tag}{dir}", match),
  110. Etag: etag,
  111. Files: files,
  112. LineFmt: "%s#cl-%d",
  113. ProjectName: match["repo"],
  114. ProjectRoot: expand("bitbucket.org/{owner}/{repo}", match),
  115. ProjectURL: expand("https://bitbucket.org/{owner}/{repo}/", match),
  116. Subdirectories: contents.Directories,
  117. VCS: match["vcs"],
  118. Status: status,
  119. Fork: repo.IsFork,
  120. Stars: repo.Followers,
  121. }, nil
  122. }
  123. func getBitbucketRepo(c *httpClient, match map[string]string) (*bitbucketRepo, error) {
  124. var repo bitbucketRepo
  125. if _, err := c.getJSON(expand("https://api.bitbucket.org/1.0/repositories/{owner}/{repo}", match), &repo); err != nil {
  126. return nil, err
  127. }
  128. return &repo, nil
  129. }
  130. func isBitbucketDeadEndFork(repo *bitbucketRepo) bool {
  131. l := "2006-01-02T15:04:05.999999999"
  132. created, err := time.Parse(l, repo.CreatedOn)
  133. if err != nil {
  134. return false
  135. }
  136. updated, err := time.Parse(l, repo.LastUpdated)
  137. if err != nil {
  138. return false
  139. }
  140. isDeadEndFork := false
  141. if repo.ForkOf.Scm != "" && created.Unix() >= updated.Unix() {
  142. isDeadEndFork = true
  143. }
  144. return isDeadEndFork
  145. }