Não pode escolher mais do que 25 tópicos Os tópicos devem começar com uma letra ou um número, podem incluir traços ('-') e podem ter até 35 caracteres.
 
 
 

357 linhas
8.5 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. // +build !appengine
  7. package gosrc
  8. import (
  9. "bytes"
  10. "errors"
  11. "io/ioutil"
  12. "log"
  13. "net/http"
  14. "os"
  15. "os/exec"
  16. "path"
  17. "path/filepath"
  18. "regexp"
  19. "strings"
  20. "time"
  21. )
  22. func init() {
  23. addService(&service{
  24. pattern: regexp.MustCompile(`^(?P<repo>(?:[a-z0-9.\-]+\.)+[a-z0-9.\-]+(?::[0-9]+)?/[A-Za-z0-9_.\-/]*?)\.(?P<vcs>bzr|git|hg|svn)(?P<dir>/[A-Za-z0-9_.\-/]*)?$`),
  25. prefix: "",
  26. get: getVCSDir,
  27. })
  28. getVCSDirFn = getVCSDir
  29. }
  30. const (
  31. lsRemoteTimeout = 5 * time.Minute
  32. cloneTimeout = 10 * time.Minute
  33. fetchTimeout = 5 * time.Minute
  34. checkoutTimeout = 1 * time.Minute
  35. )
  36. // Store temporary data in this directory.
  37. var TempDir = filepath.Join(os.TempDir(), "gddo")
  38. type urlTemplates struct {
  39. re *regexp.Regexp
  40. fileBrowse string
  41. project string
  42. line string
  43. }
  44. var vcsServices = []*urlTemplates{
  45. {
  46. regexp.MustCompile(`^git\.gitorious\.org/(?P<repo>[^/]+/[^/]+)$`),
  47. "https://gitorious.org/{repo}/blobs/{tag}/{dir}{0}",
  48. "https://gitorious.org/{repo}",
  49. "%s#line%d",
  50. },
  51. {
  52. regexp.MustCompile(`^git\.oschina\.net/(?P<repo>[^/]+/[^/]+)$`),
  53. "http://git.oschina.net/{repo}/blob/{tag}/{dir}{0}",
  54. "http://git.oschina.net/{repo}",
  55. "%s#L%d",
  56. },
  57. {
  58. regexp.MustCompile(`^(?P<r1>[^.]+)\.googlesource.com/(?P<r2>[^./]+)$`),
  59. "https://{r1}.googlesource.com/{r2}/+/{tag}/{dir}{0}",
  60. "https://{r1}.googlesource.com/{r2}/+/{tag}",
  61. "%s#%d",
  62. },
  63. {
  64. regexp.MustCompile(`^gitcafe.com/(?P<repo>[^/]+/.[^/]+)$`),
  65. "https://gitcafe.com/{repo}/tree/{tag}/{dir}{0}",
  66. "https://gitcafe.com/{repo}",
  67. "",
  68. },
  69. }
  70. // lookupURLTemplate finds an expand() template, match map and line number
  71. // format for well known repositories.
  72. func lookupURLTemplate(repo, dir, tag string) (*urlTemplates, map[string]string) {
  73. if strings.HasPrefix(dir, "/") {
  74. dir = dir[1:] + "/"
  75. }
  76. for _, t := range vcsServices {
  77. if m := t.re.FindStringSubmatch(repo); m != nil {
  78. match := map[string]string{
  79. "dir": dir,
  80. "tag": tag,
  81. }
  82. for i, name := range t.re.SubexpNames() {
  83. if name != "" {
  84. match[name] = m[i]
  85. }
  86. }
  87. return t, match
  88. }
  89. }
  90. return &urlTemplates{}, nil
  91. }
  92. type vcsCmd struct {
  93. schemes []string
  94. download func(schemes []string, clonePath, repo, savedEtag string) (tag, etag string, err error)
  95. }
  96. var vcsCmds = map[string]*vcsCmd{
  97. "git": {
  98. schemes: []string{"http", "https", "ssh", "git"},
  99. download: downloadGit,
  100. },
  101. "svn": {
  102. schemes: []string{"http", "https", "svn"},
  103. download: downloadSVN,
  104. },
  105. }
  106. var lsremoteRe = regexp.MustCompile(`(?m)^([0-9a-f]{40})\s+refs/(?:tags|heads)/(.+)$`)
  107. func downloadGit(schemes []string, clonePath, repo, savedEtag string) (string, string, error) {
  108. var p []byte
  109. var scheme string
  110. for i := range schemes {
  111. cmd := exec.Command("git", "ls-remote", "--heads", "--tags", schemes[i]+"://"+clonePath)
  112. log.Println(strings.Join(cmd.Args, " "))
  113. var err error
  114. p, err = outputWithTimeout(cmd, lsRemoteTimeout)
  115. if err == nil {
  116. scheme = schemes[i]
  117. break
  118. }
  119. }
  120. if scheme == "" {
  121. return "", "", NotFoundError{Message: "VCS not found"}
  122. }
  123. tags := make(map[string]string)
  124. for _, m := range lsremoteRe.FindAllSubmatch(p, -1) {
  125. tags[string(m[2])] = string(m[1])
  126. }
  127. tag, commit, err := bestTag(tags, "master")
  128. if err != nil {
  129. return "", "", err
  130. }
  131. etag := scheme + "-" + commit
  132. if etag == savedEtag {
  133. return "", "", NotModifiedError{}
  134. }
  135. dir := filepath.Join(TempDir, repo+".git")
  136. p, err = ioutil.ReadFile(filepath.Join(dir, ".git", "HEAD"))
  137. switch {
  138. case err != nil:
  139. if err := os.MkdirAll(dir, 0777); err != nil {
  140. return "", "", err
  141. }
  142. cmd := exec.Command("git", "clone", scheme+"://"+clonePath, dir)
  143. log.Println(strings.Join(cmd.Args, " "))
  144. if err := runWithTimeout(cmd, cloneTimeout); err != nil {
  145. return "", "", err
  146. }
  147. case string(bytes.TrimRight(p, "\n")) == commit:
  148. return tag, etag, nil
  149. default:
  150. cmd := exec.Command("git", "fetch")
  151. log.Println(strings.Join(cmd.Args, " "))
  152. cmd.Dir = dir
  153. if err := runWithTimeout(cmd, fetchTimeout); err != nil {
  154. return "", "", err
  155. }
  156. }
  157. cmd := exec.Command("git", "checkout", "--detach", "--force", commit)
  158. cmd.Dir = dir
  159. if err := runWithTimeout(cmd, checkoutTimeout); err != nil {
  160. return "", "", err
  161. }
  162. return tag, etag, nil
  163. }
  164. func downloadSVN(schemes []string, clonePath, repo, savedEtag string) (string, string, error) {
  165. var scheme string
  166. var revno string
  167. for i := range schemes {
  168. var err error
  169. revno, err = getSVNRevision(schemes[i] + "://" + clonePath)
  170. if err == nil {
  171. scheme = schemes[i]
  172. break
  173. }
  174. }
  175. if scheme == "" {
  176. return "", "", NotFoundError{Message: "VCS not found"}
  177. }
  178. etag := scheme + "-" + revno
  179. if etag == savedEtag {
  180. return "", "", NotModifiedError{}
  181. }
  182. dir := filepath.Join(TempDir, repo+".svn")
  183. localRevno, err := getSVNRevision(dir)
  184. switch {
  185. case err != nil:
  186. log.Printf("err: %v", err)
  187. if err := os.MkdirAll(dir, 0777); err != nil {
  188. return "", "", err
  189. }
  190. cmd := exec.Command("svn", "checkout", scheme+"://"+clonePath, "-r", revno, dir)
  191. log.Println(strings.Join(cmd.Args, " "))
  192. if err := runWithTimeout(cmd, cloneTimeout); err != nil {
  193. return "", "", err
  194. }
  195. case localRevno != revno:
  196. cmd := exec.Command("svn", "update", "-r", revno)
  197. log.Println(strings.Join(cmd.Args, " "))
  198. cmd.Dir = dir
  199. if err := runWithTimeout(cmd, fetchTimeout); err != nil {
  200. return "", "", err
  201. }
  202. }
  203. return "", etag, nil
  204. }
  205. var svnrevRe = regexp.MustCompile(`(?m)^Last Changed Rev: ([0-9]+)$`)
  206. func getSVNRevision(target string) (string, error) {
  207. cmd := exec.Command("svn", "info", target)
  208. log.Println(strings.Join(cmd.Args, " "))
  209. out, err := outputWithTimeout(cmd, lsRemoteTimeout)
  210. if err != nil {
  211. return "", err
  212. }
  213. match := svnrevRe.FindStringSubmatch(string(out))
  214. if match != nil {
  215. return match[1], nil
  216. }
  217. return "", NotFoundError{Message: "Last changed revision not found"}
  218. }
  219. func getVCSDir(client *http.Client, match map[string]string, etagSaved string) (*Directory, error) {
  220. cmd := vcsCmds[match["vcs"]]
  221. if cmd == nil {
  222. return nil, NotFoundError{Message: expand("VCS not supported: {vcs}", match)}
  223. }
  224. scheme := match["scheme"]
  225. if scheme == "" {
  226. i := strings.Index(etagSaved, "-")
  227. if i > 0 {
  228. scheme = etagSaved[:i]
  229. }
  230. }
  231. schemes := cmd.schemes
  232. if scheme != "" {
  233. for i := range cmd.schemes {
  234. if cmd.schemes[i] == scheme {
  235. schemes = cmd.schemes[i : i+1]
  236. break
  237. }
  238. }
  239. }
  240. clonePath, ok := match["clonePath"]
  241. if !ok {
  242. // clonePath may be unset if we're being called via the generic repo.vcs/dir regexp matcher.
  243. // In that case, set it to the repo value.
  244. clonePath = match["repo"]
  245. }
  246. // Download and checkout.
  247. tag, etag, err := cmd.download(schemes, clonePath, match["repo"], etagSaved)
  248. if err != nil {
  249. return nil, err
  250. }
  251. // Find source location.
  252. template, urlMatch := lookupURLTemplate(match["repo"], match["dir"], tag)
  253. // Slurp source files.
  254. d := filepath.Join(TempDir, filepath.FromSlash(expand("{repo}.{vcs}", match)), filepath.FromSlash(match["dir"]))
  255. f, err := os.Open(d)
  256. if err != nil {
  257. if os.IsNotExist(err) {
  258. err = NotFoundError{Message: err.Error()}
  259. }
  260. return nil, err
  261. }
  262. fis, err := f.Readdir(-1)
  263. if err != nil {
  264. return nil, err
  265. }
  266. var files []*File
  267. var subdirs []string
  268. for _, fi := range fis {
  269. switch {
  270. case fi.IsDir():
  271. if isValidPathElement(fi.Name()) {
  272. subdirs = append(subdirs, fi.Name())
  273. }
  274. case isDocFile(fi.Name()):
  275. b, err := ioutil.ReadFile(filepath.Join(d, fi.Name()))
  276. if err != nil {
  277. return nil, err
  278. }
  279. files = append(files, &File{
  280. Name: fi.Name(),
  281. BrowseURL: expand(template.fileBrowse, urlMatch, fi.Name()),
  282. Data: b,
  283. })
  284. }
  285. }
  286. return &Directory{
  287. LineFmt: template.line,
  288. ProjectRoot: expand("{repo}.{vcs}", match),
  289. ProjectName: path.Base(match["repo"]),
  290. ProjectURL: expand(template.project, urlMatch),
  291. BrowseURL: "",
  292. Etag: etag,
  293. VCS: match["vcs"],
  294. Subdirectories: subdirs,
  295. Files: files,
  296. }, nil
  297. }
  298. func runWithTimeout(cmd *exec.Cmd, timeout time.Duration) error {
  299. if err := cmd.Start(); err != nil {
  300. return err
  301. }
  302. t := time.AfterFunc(timeout, func() { cmd.Process.Kill() })
  303. defer t.Stop()
  304. return cmd.Wait()
  305. }
  306. func outputWithTimeout(cmd *exec.Cmd, timeout time.Duration) ([]byte, error) {
  307. if cmd.Stdout != nil {
  308. return nil, errors.New("exec: Stdout already set")
  309. }
  310. var b bytes.Buffer
  311. cmd.Stdout = &b
  312. err := runWithTimeout(cmd, timeout)
  313. return b.Bytes(), err
  314. }