本站源代码
Vous ne pouvez pas sélectionner plus de 25 sujets Les noms de sujets doivent commencer par une lettre ou un nombre, peuvent contenir des tirets ('-') et peuvent comporter jusqu'à 35 caractères.

507 lignes
13KB

  1. // Copyright 2019 The Gitea Authors. All rights reserved.
  2. // Copyright 2018 Jonas Franz. All rights reserved.
  3. // Use of this source code is governed by a MIT-style
  4. // license that can be found in the LICENSE file.
  5. package migrations
  6. import (
  7. "context"
  8. "fmt"
  9. "net/http"
  10. "net/url"
  11. "strings"
  12. "code.gitea.io/gitea/modules/log"
  13. "code.gitea.io/gitea/modules/migrations/base"
  14. "code.gitea.io/gitea/modules/structs"
  15. "github.com/google/go-github/v24/github"
  16. "golang.org/x/oauth2"
  17. )
  18. var (
  19. _ base.Downloader = &GithubDownloaderV3{}
  20. _ base.DownloaderFactory = &GithubDownloaderV3Factory{}
  21. )
  22. func init() {
  23. RegisterDownloaderFactory(&GithubDownloaderV3Factory{})
  24. }
  25. // GithubDownloaderV3Factory defines a github downloader v3 factory
  26. type GithubDownloaderV3Factory struct {
  27. }
  28. // Match returns ture if the migration remote URL matched this downloader factory
  29. func (f *GithubDownloaderV3Factory) Match(opts base.MigrateOptions) (bool, error) {
  30. u, err := url.Parse(opts.CloneAddr)
  31. if err != nil {
  32. return false, err
  33. }
  34. return strings.EqualFold(u.Host, "github.com") && opts.AuthUsername != "", nil
  35. }
  36. // New returns a Downloader related to this factory according MigrateOptions
  37. func (f *GithubDownloaderV3Factory) New(opts base.MigrateOptions) (base.Downloader, error) {
  38. u, err := url.Parse(opts.CloneAddr)
  39. if err != nil {
  40. return nil, err
  41. }
  42. fields := strings.Split(u.Path, "/")
  43. oldOwner := fields[1]
  44. oldName := strings.TrimSuffix(fields[2], ".git")
  45. log.Trace("Create github downloader: %s/%s", oldOwner, oldName)
  46. return NewGithubDownloaderV3(opts.AuthUsername, opts.AuthPassword, oldOwner, oldName), nil
  47. }
  48. // GitServiceType returns the type of git service
  49. func (f *GithubDownloaderV3Factory) GitServiceType() structs.GitServiceType {
  50. return structs.GithubService
  51. }
  52. // GithubDownloaderV3 implements a Downloader interface to get repository informations
  53. // from github via APIv3
  54. type GithubDownloaderV3 struct {
  55. ctx context.Context
  56. client *github.Client
  57. repoOwner string
  58. repoName string
  59. userName string
  60. password string
  61. }
  62. // NewGithubDownloaderV3 creates a github Downloader via github v3 API
  63. func NewGithubDownloaderV3(userName, password, repoOwner, repoName string) *GithubDownloaderV3 {
  64. var downloader = GithubDownloaderV3{
  65. userName: userName,
  66. password: password,
  67. ctx: context.Background(),
  68. repoOwner: repoOwner,
  69. repoName: repoName,
  70. }
  71. var client *http.Client
  72. if userName != "" {
  73. if password == "" {
  74. ts := oauth2.StaticTokenSource(
  75. &oauth2.Token{AccessToken: userName},
  76. )
  77. client = oauth2.NewClient(downloader.ctx, ts)
  78. } else {
  79. client = &http.Client{
  80. Transport: &http.Transport{
  81. Proxy: func(req *http.Request) (*url.URL, error) {
  82. req.SetBasicAuth(userName, password)
  83. return nil, nil
  84. },
  85. },
  86. }
  87. }
  88. }
  89. downloader.client = github.NewClient(client)
  90. return &downloader
  91. }
  92. // GetRepoInfo returns a repository information
  93. func (g *GithubDownloaderV3) GetRepoInfo() (*base.Repository, error) {
  94. gr, _, err := g.client.Repositories.Get(g.ctx, g.repoOwner, g.repoName)
  95. if err != nil {
  96. return nil, err
  97. }
  98. // convert github repo to stand Repo
  99. return &base.Repository{
  100. Owner: g.repoOwner,
  101. Name: gr.GetName(),
  102. IsPrivate: *gr.Private,
  103. Description: gr.GetDescription(),
  104. OriginalURL: gr.GetHTMLURL(),
  105. CloneURL: gr.GetCloneURL(),
  106. }, nil
  107. }
  108. // GetTopics return github topics
  109. func (g *GithubDownloaderV3) GetTopics() ([]string, error) {
  110. r, _, err := g.client.Repositories.Get(g.ctx, g.repoOwner, g.repoName)
  111. return r.Topics, err
  112. }
  113. // GetMilestones returns milestones
  114. func (g *GithubDownloaderV3) GetMilestones() ([]*base.Milestone, error) {
  115. var perPage = 100
  116. var milestones = make([]*base.Milestone, 0, perPage)
  117. for i := 1; ; i++ {
  118. ms, _, err := g.client.Issues.ListMilestones(g.ctx, g.repoOwner, g.repoName,
  119. &github.MilestoneListOptions{
  120. State: "all",
  121. ListOptions: github.ListOptions{
  122. Page: i,
  123. PerPage: perPage,
  124. }})
  125. if err != nil {
  126. return nil, err
  127. }
  128. for _, m := range ms {
  129. var desc string
  130. if m.Description != nil {
  131. desc = *m.Description
  132. }
  133. var state = "open"
  134. if m.State != nil {
  135. state = *m.State
  136. }
  137. milestones = append(milestones, &base.Milestone{
  138. Title: *m.Title,
  139. Description: desc,
  140. Deadline: m.DueOn,
  141. State: state,
  142. Created: *m.CreatedAt,
  143. Updated: m.UpdatedAt,
  144. Closed: m.ClosedAt,
  145. })
  146. }
  147. if len(ms) < perPage {
  148. break
  149. }
  150. }
  151. return milestones, nil
  152. }
  153. func convertGithubLabel(label *github.Label) *base.Label {
  154. var desc string
  155. if label.Description != nil {
  156. desc = *label.Description
  157. }
  158. return &base.Label{
  159. Name: *label.Name,
  160. Color: *label.Color,
  161. Description: desc,
  162. }
  163. }
  164. // GetLabels returns labels
  165. func (g *GithubDownloaderV3) GetLabels() ([]*base.Label, error) {
  166. var perPage = 100
  167. var labels = make([]*base.Label, 0, perPage)
  168. for i := 1; ; i++ {
  169. ls, _, err := g.client.Issues.ListLabels(g.ctx, g.repoOwner, g.repoName,
  170. &github.ListOptions{
  171. Page: i,
  172. PerPage: perPage,
  173. })
  174. if err != nil {
  175. return nil, err
  176. }
  177. for _, label := range ls {
  178. labels = append(labels, convertGithubLabel(label))
  179. }
  180. if len(ls) < perPage {
  181. break
  182. }
  183. }
  184. return labels, nil
  185. }
  186. func (g *GithubDownloaderV3) convertGithubRelease(rel *github.RepositoryRelease) *base.Release {
  187. var (
  188. name string
  189. desc string
  190. )
  191. if rel.Body != nil {
  192. desc = *rel.Body
  193. }
  194. if rel.Name != nil {
  195. name = *rel.Name
  196. }
  197. var email string
  198. if rel.Author.Email != nil {
  199. email = *rel.Author.Email
  200. }
  201. r := &base.Release{
  202. TagName: *rel.TagName,
  203. TargetCommitish: *rel.TargetCommitish,
  204. Name: name,
  205. Body: desc,
  206. Draft: *rel.Draft,
  207. Prerelease: *rel.Prerelease,
  208. Created: rel.CreatedAt.Time,
  209. PublisherID: *rel.Author.ID,
  210. PublisherName: *rel.Author.Login,
  211. PublisherEmail: email,
  212. Published: rel.PublishedAt.Time,
  213. }
  214. for _, asset := range rel.Assets {
  215. u, _ := url.Parse(*asset.BrowserDownloadURL)
  216. u.User = url.UserPassword(g.userName, g.password)
  217. r.Assets = append(r.Assets, base.ReleaseAsset{
  218. URL: u.String(),
  219. Name: *asset.Name,
  220. ContentType: asset.ContentType,
  221. Size: asset.Size,
  222. DownloadCount: asset.DownloadCount,
  223. Created: asset.CreatedAt.Time,
  224. Updated: asset.UpdatedAt.Time,
  225. })
  226. }
  227. return r
  228. }
  229. // GetReleases returns releases
  230. func (g *GithubDownloaderV3) GetReleases() ([]*base.Release, error) {
  231. var perPage = 100
  232. var releases = make([]*base.Release, 0, perPage)
  233. for i := 1; ; i++ {
  234. ls, _, err := g.client.Repositories.ListReleases(g.ctx, g.repoOwner, g.repoName,
  235. &github.ListOptions{
  236. Page: i,
  237. PerPage: perPage,
  238. })
  239. if err != nil {
  240. return nil, err
  241. }
  242. for _, release := range ls {
  243. releases = append(releases, g.convertGithubRelease(release))
  244. }
  245. if len(ls) < perPage {
  246. break
  247. }
  248. }
  249. return releases, nil
  250. }
  251. func convertGithubReactions(reactions *github.Reactions) *base.Reactions {
  252. return &base.Reactions{
  253. TotalCount: *reactions.TotalCount,
  254. PlusOne: *reactions.PlusOne,
  255. MinusOne: *reactions.MinusOne,
  256. Laugh: *reactions.Laugh,
  257. Confused: *reactions.Confused,
  258. Heart: *reactions.Heart,
  259. Hooray: *reactions.Hooray,
  260. }
  261. }
  262. // GetIssues returns issues according start and limit
  263. func (g *GithubDownloaderV3) GetIssues(page, perPage int) ([]*base.Issue, bool, error) {
  264. opt := &github.IssueListByRepoOptions{
  265. Sort: "created",
  266. Direction: "asc",
  267. State: "all",
  268. ListOptions: github.ListOptions{
  269. PerPage: perPage,
  270. Page: page,
  271. },
  272. }
  273. var allIssues = make([]*base.Issue, 0, perPage)
  274. issues, _, err := g.client.Issues.ListByRepo(g.ctx, g.repoOwner, g.repoName, opt)
  275. if err != nil {
  276. return nil, false, fmt.Errorf("error while listing repos: %v", err)
  277. }
  278. for _, issue := range issues {
  279. if issue.IsPullRequest() {
  280. continue
  281. }
  282. var body string
  283. if issue.Body != nil {
  284. body = *issue.Body
  285. }
  286. var milestone string
  287. if issue.Milestone != nil {
  288. milestone = *issue.Milestone.Title
  289. }
  290. var labels = make([]*base.Label, 0, len(issue.Labels))
  291. for _, l := range issue.Labels {
  292. labels = append(labels, convertGithubLabel(&l))
  293. }
  294. var reactions *base.Reactions
  295. if issue.Reactions != nil {
  296. reactions = convertGithubReactions(issue.Reactions)
  297. }
  298. var email string
  299. if issue.User.Email != nil {
  300. email = *issue.User.Email
  301. }
  302. allIssues = append(allIssues, &base.Issue{
  303. Title: *issue.Title,
  304. Number: int64(*issue.Number),
  305. PosterID: *issue.User.ID,
  306. PosterName: *issue.User.Login,
  307. PosterEmail: email,
  308. Content: body,
  309. Milestone: milestone,
  310. State: *issue.State,
  311. Created: *issue.CreatedAt,
  312. Labels: labels,
  313. Reactions: reactions,
  314. Closed: issue.ClosedAt,
  315. IsLocked: *issue.Locked,
  316. })
  317. }
  318. return allIssues, len(issues) < perPage, nil
  319. }
  320. // GetComments returns comments according issueNumber
  321. func (g *GithubDownloaderV3) GetComments(issueNumber int64) ([]*base.Comment, error) {
  322. var allComments = make([]*base.Comment, 0, 100)
  323. opt := &github.IssueListCommentsOptions{
  324. Sort: "created",
  325. Direction: "asc",
  326. ListOptions: github.ListOptions{
  327. PerPage: 100,
  328. },
  329. }
  330. for {
  331. comments, resp, err := g.client.Issues.ListComments(g.ctx, g.repoOwner, g.repoName, int(issueNumber), opt)
  332. if err != nil {
  333. return nil, fmt.Errorf("error while listing repos: %v", err)
  334. }
  335. for _, comment := range comments {
  336. var email string
  337. if comment.User.Email != nil {
  338. email = *comment.User.Email
  339. }
  340. var reactions *base.Reactions
  341. if comment.Reactions != nil {
  342. reactions = convertGithubReactions(comment.Reactions)
  343. }
  344. allComments = append(allComments, &base.Comment{
  345. IssueIndex: issueNumber,
  346. PosterID: *comment.User.ID,
  347. PosterName: *comment.User.Login,
  348. PosterEmail: email,
  349. Content: *comment.Body,
  350. Created: *comment.CreatedAt,
  351. Reactions: reactions,
  352. })
  353. }
  354. if resp.NextPage == 0 {
  355. break
  356. }
  357. opt.Page = resp.NextPage
  358. }
  359. return allComments, nil
  360. }
  361. // GetPullRequests returns pull requests according page and perPage
  362. func (g *GithubDownloaderV3) GetPullRequests(page, perPage int) ([]*base.PullRequest, error) {
  363. opt := &github.PullRequestListOptions{
  364. Sort: "created",
  365. Direction: "asc",
  366. State: "all",
  367. ListOptions: github.ListOptions{
  368. PerPage: perPage,
  369. Page: page,
  370. },
  371. }
  372. var allPRs = make([]*base.PullRequest, 0, perPage)
  373. prs, _, err := g.client.PullRequests.List(g.ctx, g.repoOwner, g.repoName, opt)
  374. if err != nil {
  375. return nil, fmt.Errorf("error while listing repos: %v", err)
  376. }
  377. for _, pr := range prs {
  378. var body string
  379. if pr.Body != nil {
  380. body = *pr.Body
  381. }
  382. var milestone string
  383. if pr.Milestone != nil {
  384. milestone = *pr.Milestone.Title
  385. }
  386. var labels = make([]*base.Label, 0, len(pr.Labels))
  387. for _, l := range pr.Labels {
  388. labels = append(labels, convertGithubLabel(l))
  389. }
  390. // FIXME: This API missing reactions, we may need another extra request to get reactions
  391. var email string
  392. if pr.User.Email != nil {
  393. email = *pr.User.Email
  394. }
  395. var merged bool
  396. // pr.Merged is not valid, so use MergedAt to test if it's merged
  397. if pr.MergedAt != nil {
  398. merged = true
  399. }
  400. var (
  401. headRepoName string
  402. cloneURL string
  403. headRef string
  404. headSHA string
  405. )
  406. if pr.Head.Repo != nil {
  407. if pr.Head.Repo.Name != nil {
  408. headRepoName = *pr.Head.Repo.Name
  409. }
  410. if pr.Head.Repo.CloneURL != nil {
  411. cloneURL = *pr.Head.Repo.CloneURL
  412. }
  413. }
  414. if pr.Head.Ref != nil {
  415. headRef = *pr.Head.Ref
  416. }
  417. if pr.Head.SHA != nil {
  418. headSHA = *pr.Head.SHA
  419. }
  420. var mergeCommitSHA string
  421. if pr.MergeCommitSHA != nil {
  422. mergeCommitSHA = *pr.MergeCommitSHA
  423. }
  424. var headUserName string
  425. if pr.Head.User != nil && pr.Head.User.Login != nil {
  426. headUserName = *pr.Head.User.Login
  427. }
  428. allPRs = append(allPRs, &base.PullRequest{
  429. Title: *pr.Title,
  430. Number: int64(*pr.Number),
  431. PosterName: *pr.User.Login,
  432. PosterID: *pr.User.ID,
  433. PosterEmail: email,
  434. Content: body,
  435. Milestone: milestone,
  436. State: *pr.State,
  437. Created: *pr.CreatedAt,
  438. Closed: pr.ClosedAt,
  439. Labels: labels,
  440. Merged: merged,
  441. MergeCommitSHA: mergeCommitSHA,
  442. MergedTime: pr.MergedAt,
  443. IsLocked: pr.ActiveLockReason != nil,
  444. Head: base.PullRequestBranch{
  445. Ref: headRef,
  446. SHA: headSHA,
  447. RepoName: headRepoName,
  448. OwnerName: headUserName,
  449. CloneURL: cloneURL,
  450. },
  451. Base: base.PullRequestBranch{
  452. Ref: *pr.Base.Ref,
  453. SHA: *pr.Base.SHA,
  454. RepoName: *pr.Base.Repo.Name,
  455. OwnerName: *pr.Base.User.Login,
  456. },
  457. PatchURL: *pr.PatchURL,
  458. })
  459. }
  460. return allPRs, nil
  461. }
上海开阖软件有限公司 沪ICP备12045867号-1