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.

gogs.go 8.2KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312
  1. // Copyright 2019 The Gitea Authors. All rights reserved.
  2. // Use of this source code is governed by a MIT-style
  3. // license that can be found in the LICENSE file.
  4. package migrations
  5. import (
  6. "context"
  7. "fmt"
  8. "net/http"
  9. "net/url"
  10. "strings"
  11. "time"
  12. "code.gitea.io/gitea/modules/log"
  13. base "code.gitea.io/gitea/modules/migration"
  14. "code.gitea.io/gitea/modules/proxy"
  15. "code.gitea.io/gitea/modules/structs"
  16. "github.com/gogs/go-gogs-client"
  17. )
  18. var (
  19. _ base.Downloader = &GogsDownloader{}
  20. _ base.DownloaderFactory = &GogsDownloaderFactory{}
  21. )
  22. func init() {
  23. RegisterDownloaderFactory(&GogsDownloaderFactory{})
  24. }
  25. // GogsDownloaderFactory defines a gogs downloader factory
  26. type GogsDownloaderFactory struct {
  27. }
  28. // New returns a Downloader related to this factory according MigrateOptions
  29. func (f *GogsDownloaderFactory) New(ctx context.Context, opts base.MigrateOptions) (base.Downloader, error) {
  30. u, err := url.Parse(opts.CloneAddr)
  31. if err != nil {
  32. return nil, err
  33. }
  34. baseURL := u.Scheme + "://" + u.Host
  35. repoNameSpace := strings.TrimSuffix(u.Path, ".git")
  36. repoNameSpace = strings.Trim(repoNameSpace, "/")
  37. fields := strings.Split(repoNameSpace, "/")
  38. if len(fields) < 2 {
  39. return nil, fmt.Errorf("invalid path: %s", repoNameSpace)
  40. }
  41. log.Trace("Create gogs downloader. BaseURL: %s RepoOwner: %s RepoName: %s", baseURL, fields[0], fields[1])
  42. return NewGogsDownloader(ctx, baseURL, opts.AuthUsername, opts.AuthPassword, opts.AuthToken, fields[0], fields[1]), nil
  43. }
  44. // GitServiceType returns the type of git service
  45. func (f *GogsDownloaderFactory) GitServiceType() structs.GitServiceType {
  46. return structs.GogsService
  47. }
  48. // GogsDownloader implements a Downloader interface to get repository information
  49. // from gogs via API
  50. type GogsDownloader struct {
  51. base.NullDownloader
  52. ctx context.Context
  53. client *gogs.Client
  54. baseURL string
  55. repoOwner string
  56. repoName string
  57. userName string
  58. password string
  59. openIssuesFinished bool
  60. openIssuesPages int
  61. transport http.RoundTripper
  62. }
  63. // SetContext set context
  64. func (g *GogsDownloader) SetContext(ctx context.Context) {
  65. g.ctx = ctx
  66. }
  67. // NewGogsDownloader creates a gogs Downloader via gogs API
  68. func NewGogsDownloader(ctx context.Context, baseURL, userName, password, token, repoOwner, repoName string) *GogsDownloader {
  69. var downloader = GogsDownloader{
  70. ctx: ctx,
  71. baseURL: baseURL,
  72. userName: userName,
  73. password: password,
  74. repoOwner: repoOwner,
  75. repoName: repoName,
  76. }
  77. var client *gogs.Client
  78. if len(token) != 0 {
  79. client = gogs.NewClient(baseURL, token)
  80. downloader.userName = token
  81. } else {
  82. var transport = NewMigrationHTTPTransport()
  83. transport.Proxy = func(req *http.Request) (*url.URL, error) {
  84. req.SetBasicAuth(userName, password)
  85. return proxy.Proxy()(req)
  86. }
  87. downloader.transport = transport
  88. client = gogs.NewClient(baseURL, "")
  89. client.SetHTTPClient(&http.Client{
  90. Transport: &downloader,
  91. })
  92. }
  93. downloader.client = client
  94. return &downloader
  95. }
  96. // RoundTrip wraps the provided request within this downloader's context and passes it to our internal http.Transport.
  97. // This implements http.RoundTripper and makes the gogs client requests cancellable even though it is not cancellable itself
  98. func (g *GogsDownloader) RoundTrip(req *http.Request) (*http.Response, error) {
  99. return g.transport.RoundTrip(req.WithContext(g.ctx))
  100. }
  101. // GetRepoInfo returns a repository information
  102. func (g *GogsDownloader) GetRepoInfo() (*base.Repository, error) {
  103. gr, err := g.client.GetRepo(g.repoOwner, g.repoName)
  104. if err != nil {
  105. return nil, err
  106. }
  107. // convert gogs repo to stand Repo
  108. return &base.Repository{
  109. Owner: g.repoOwner,
  110. Name: g.repoName,
  111. IsPrivate: gr.Private,
  112. Description: gr.Description,
  113. CloneURL: gr.CloneURL,
  114. OriginalURL: gr.HTMLURL,
  115. DefaultBranch: gr.DefaultBranch,
  116. }, nil
  117. }
  118. // GetMilestones returns milestones
  119. func (g *GogsDownloader) GetMilestones() ([]*base.Milestone, error) {
  120. var perPage = 100
  121. var milestones = make([]*base.Milestone, 0, perPage)
  122. ms, err := g.client.ListRepoMilestones(g.repoOwner, g.repoName)
  123. if err != nil {
  124. return nil, err
  125. }
  126. for _, m := range ms {
  127. milestones = append(milestones, &base.Milestone{
  128. Title: m.Title,
  129. Description: m.Description,
  130. Deadline: m.Deadline,
  131. State: string(m.State),
  132. Closed: m.Closed,
  133. })
  134. }
  135. return milestones, nil
  136. }
  137. // GetLabels returns labels
  138. func (g *GogsDownloader) GetLabels() ([]*base.Label, error) {
  139. var perPage = 100
  140. var labels = make([]*base.Label, 0, perPage)
  141. ls, err := g.client.ListRepoLabels(g.repoOwner, g.repoName)
  142. if err != nil {
  143. return nil, err
  144. }
  145. for _, label := range ls {
  146. labels = append(labels, convertGogsLabel(label))
  147. }
  148. return labels, nil
  149. }
  150. // GetIssues returns issues according start and limit, perPage is not supported
  151. func (g *GogsDownloader) GetIssues(page, _ int) ([]*base.Issue, bool, error) {
  152. var state string
  153. if g.openIssuesFinished {
  154. state = string(gogs.STATE_CLOSED)
  155. page -= g.openIssuesPages
  156. } else {
  157. state = string(gogs.STATE_OPEN)
  158. g.openIssuesPages = page
  159. }
  160. issues, isEnd, err := g.getIssues(page, state)
  161. if err != nil {
  162. return nil, false, err
  163. }
  164. if isEnd {
  165. if g.openIssuesFinished {
  166. return issues, true, nil
  167. }
  168. g.openIssuesFinished = true
  169. }
  170. return issues, false, nil
  171. }
  172. func (g *GogsDownloader) getIssues(page int, state string) ([]*base.Issue, bool, error) {
  173. var allIssues = make([]*base.Issue, 0, 10)
  174. issues, err := g.client.ListRepoIssues(g.repoOwner, g.repoName, gogs.ListIssueOption{
  175. Page: page,
  176. State: state,
  177. })
  178. if err != nil {
  179. return nil, false, fmt.Errorf("error while listing repos: %v", err)
  180. }
  181. for _, issue := range issues {
  182. if issue.PullRequest != nil {
  183. continue
  184. }
  185. allIssues = append(allIssues, convertGogsIssue(issue))
  186. }
  187. return allIssues, len(issues) == 0, nil
  188. }
  189. // GetComments returns comments according issueNumber
  190. func (g *GogsDownloader) GetComments(opts base.GetCommentOptions) ([]*base.Comment, bool, error) {
  191. var allComments = make([]*base.Comment, 0, 100)
  192. comments, err := g.client.ListIssueComments(g.repoOwner, g.repoName, opts.Context.ForeignID())
  193. if err != nil {
  194. return nil, false, fmt.Errorf("error while listing repos: %v", err)
  195. }
  196. for _, comment := range comments {
  197. if len(comment.Body) == 0 || comment.Poster == nil {
  198. continue
  199. }
  200. allComments = append(allComments, &base.Comment{
  201. IssueIndex: opts.Context.LocalID(),
  202. PosterID: comment.Poster.ID,
  203. PosterName: comment.Poster.Login,
  204. PosterEmail: comment.Poster.Email,
  205. Content: comment.Body,
  206. Created: comment.Created,
  207. Updated: comment.Updated,
  208. })
  209. }
  210. return allComments, true, nil
  211. }
  212. // GetTopics return repository topics
  213. func (g *GogsDownloader) GetTopics() ([]string, error) {
  214. return []string{}, nil
  215. }
  216. // FormatCloneURL add authentification into remote URLs
  217. func (g *GogsDownloader) FormatCloneURL(opts MigrateOptions, remoteAddr string) (string, error) {
  218. if len(opts.AuthToken) > 0 || len(opts.AuthUsername) > 0 {
  219. u, err := url.Parse(remoteAddr)
  220. if err != nil {
  221. return "", err
  222. }
  223. if len(opts.AuthToken) != 0 {
  224. u.User = url.UserPassword(opts.AuthToken, "")
  225. } else {
  226. u.User = url.UserPassword(opts.AuthUsername, opts.AuthPassword)
  227. }
  228. return u.String(), nil
  229. }
  230. return remoteAddr, nil
  231. }
  232. func convertGogsIssue(issue *gogs.Issue) *base.Issue {
  233. var milestone string
  234. if issue.Milestone != nil {
  235. milestone = issue.Milestone.Title
  236. }
  237. var labels = make([]*base.Label, 0, len(issue.Labels))
  238. for _, l := range issue.Labels {
  239. labels = append(labels, convertGogsLabel(l))
  240. }
  241. var closed *time.Time
  242. if issue.State == gogs.STATE_CLOSED {
  243. // gogs client haven't provide closed, so we use updated instead
  244. closed = &issue.Updated
  245. }
  246. return &base.Issue{
  247. Title: issue.Title,
  248. Number: issue.Index,
  249. PosterID: issue.Poster.ID,
  250. PosterName: issue.Poster.Login,
  251. PosterEmail: issue.Poster.Email,
  252. Content: issue.Body,
  253. Milestone: milestone,
  254. State: string(issue.State),
  255. Created: issue.Created,
  256. Updated: issue.Updated,
  257. Labels: labels,
  258. Closed: closed,
  259. Context: base.BasicIssueContext(issue.Index),
  260. }
  261. }
  262. func convertGogsLabel(label *gogs.Label) *base.Label {
  263. return &base.Label{
  264. Name: label.Name,
  265. Color: label.Color,
  266. }
  267. }