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.

github.go 21KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774
  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. "crypto/tls"
  9. "fmt"
  10. "io"
  11. "net/http"
  12. "net/url"
  13. "strconv"
  14. "strings"
  15. "time"
  16. "code.gitea.io/gitea/modules/log"
  17. "code.gitea.io/gitea/modules/migrations/base"
  18. "code.gitea.io/gitea/modules/proxy"
  19. "code.gitea.io/gitea/modules/setting"
  20. "code.gitea.io/gitea/modules/structs"
  21. "code.gitea.io/gitea/modules/util"
  22. "github.com/google/go-github/v37/github"
  23. "golang.org/x/oauth2"
  24. )
  25. var (
  26. _ base.Downloader = &GithubDownloaderV3{}
  27. _ base.DownloaderFactory = &GithubDownloaderV3Factory{}
  28. // GithubLimitRateRemaining limit to wait for new rate to apply
  29. GithubLimitRateRemaining = 0
  30. )
  31. func init() {
  32. RegisterDownloaderFactory(&GithubDownloaderV3Factory{})
  33. }
  34. // GithubDownloaderV3Factory defines a github downloader v3 factory
  35. type GithubDownloaderV3Factory struct {
  36. }
  37. // New returns a Downloader related to this factory according MigrateOptions
  38. func (f *GithubDownloaderV3Factory) New(ctx context.Context, opts base.MigrateOptions) (base.Downloader, error) {
  39. u, err := url.Parse(opts.CloneAddr)
  40. if err != nil {
  41. return nil, err
  42. }
  43. baseURL := u.Scheme + "://" + u.Host
  44. fields := strings.Split(u.Path, "/")
  45. oldOwner := fields[1]
  46. oldName := strings.TrimSuffix(fields[2], ".git")
  47. log.Trace("Create github downloader: %s/%s", oldOwner, oldName)
  48. return NewGithubDownloaderV3(ctx, baseURL, opts.AuthUsername, opts.AuthPassword, opts.AuthToken, oldOwner, oldName), nil
  49. }
  50. // GitServiceType returns the type of git service
  51. func (f *GithubDownloaderV3Factory) GitServiceType() structs.GitServiceType {
  52. return structs.GithubService
  53. }
  54. // GithubDownloaderV3 implements a Downloader interface to get repository information
  55. // from github via APIv3
  56. type GithubDownloaderV3 struct {
  57. base.NullDownloader
  58. ctx context.Context
  59. client *github.Client
  60. repoOwner string
  61. repoName string
  62. userName string
  63. password string
  64. rate *github.Rate
  65. maxPerPage int
  66. }
  67. // NewGithubDownloaderV3 creates a github Downloader via github v3 API
  68. func NewGithubDownloaderV3(ctx context.Context, baseURL, userName, password, token, repoOwner, repoName string) *GithubDownloaderV3 {
  69. var downloader = GithubDownloaderV3{
  70. userName: userName,
  71. password: password,
  72. ctx: ctx,
  73. repoOwner: repoOwner,
  74. repoName: repoName,
  75. maxPerPage: 100,
  76. }
  77. client := &http.Client{
  78. Transport: &http.Transport{
  79. Proxy: func(req *http.Request) (*url.URL, error) {
  80. req.SetBasicAuth(userName, password)
  81. return proxy.Proxy()(req)
  82. },
  83. },
  84. }
  85. if token != "" {
  86. ts := oauth2.StaticTokenSource(
  87. &oauth2.Token{AccessToken: token},
  88. )
  89. client = oauth2.NewClient(downloader.ctx, ts)
  90. }
  91. downloader.client = github.NewClient(client)
  92. if baseURL != "https://github.com" {
  93. downloader.client, _ = github.NewEnterpriseClient(baseURL, baseURL, client)
  94. }
  95. return &downloader
  96. }
  97. // SetContext set context
  98. func (g *GithubDownloaderV3) SetContext(ctx context.Context) {
  99. g.ctx = ctx
  100. }
  101. func (g *GithubDownloaderV3) sleep() {
  102. for g.rate != nil && g.rate.Remaining <= GithubLimitRateRemaining {
  103. timer := time.NewTimer(time.Until(g.rate.Reset.Time))
  104. select {
  105. case <-g.ctx.Done():
  106. util.StopTimer(timer)
  107. return
  108. case <-timer.C:
  109. }
  110. err := g.RefreshRate()
  111. if err != nil {
  112. log.Error("g.client.RateLimits: %s", err)
  113. }
  114. }
  115. }
  116. // RefreshRate update the current rate (doesn't count in rate limit)
  117. func (g *GithubDownloaderV3) RefreshRate() error {
  118. rates, _, err := g.client.RateLimits(g.ctx)
  119. if err != nil {
  120. // if rate limit is not enabled, ignore it
  121. if strings.Contains(err.Error(), "404") {
  122. g.rate = nil
  123. return nil
  124. }
  125. return err
  126. }
  127. g.rate = rates.GetCore()
  128. return nil
  129. }
  130. // GetRepoInfo returns a repository information
  131. func (g *GithubDownloaderV3) GetRepoInfo() (*base.Repository, error) {
  132. g.sleep()
  133. gr, resp, err := g.client.Repositories.Get(g.ctx, g.repoOwner, g.repoName)
  134. if err != nil {
  135. return nil, err
  136. }
  137. g.rate = &resp.Rate
  138. // convert github repo to stand Repo
  139. return &base.Repository{
  140. Owner: g.repoOwner,
  141. Name: gr.GetName(),
  142. IsPrivate: gr.GetPrivate(),
  143. Description: gr.GetDescription(),
  144. OriginalURL: gr.GetHTMLURL(),
  145. CloneURL: gr.GetCloneURL(),
  146. DefaultBranch: gr.GetDefaultBranch(),
  147. }, nil
  148. }
  149. // GetTopics return github topics
  150. func (g *GithubDownloaderV3) GetTopics() ([]string, error) {
  151. g.sleep()
  152. r, resp, err := g.client.Repositories.Get(g.ctx, g.repoOwner, g.repoName)
  153. if err != nil {
  154. return nil, err
  155. }
  156. g.rate = &resp.Rate
  157. return r.Topics, nil
  158. }
  159. // GetMilestones returns milestones
  160. func (g *GithubDownloaderV3) GetMilestones() ([]*base.Milestone, error) {
  161. var perPage = g.maxPerPage
  162. var milestones = make([]*base.Milestone, 0, perPage)
  163. for i := 1; ; i++ {
  164. g.sleep()
  165. ms, resp, err := g.client.Issues.ListMilestones(g.ctx, g.repoOwner, g.repoName,
  166. &github.MilestoneListOptions{
  167. State: "all",
  168. ListOptions: github.ListOptions{
  169. Page: i,
  170. PerPage: perPage,
  171. }})
  172. if err != nil {
  173. return nil, err
  174. }
  175. g.rate = &resp.Rate
  176. for _, m := range ms {
  177. var state = "open"
  178. if m.State != nil {
  179. state = *m.State
  180. }
  181. milestones = append(milestones, &base.Milestone{
  182. Title: m.GetTitle(),
  183. Description: m.GetDescription(),
  184. Deadline: m.DueOn,
  185. State: state,
  186. Created: m.GetCreatedAt(),
  187. Updated: m.UpdatedAt,
  188. Closed: m.ClosedAt,
  189. })
  190. }
  191. if len(ms) < perPage {
  192. break
  193. }
  194. }
  195. return milestones, nil
  196. }
  197. func convertGithubLabel(label *github.Label) *base.Label {
  198. return &base.Label{
  199. Name: label.GetName(),
  200. Color: label.GetColor(),
  201. Description: label.GetDescription(),
  202. }
  203. }
  204. // GetLabels returns labels
  205. func (g *GithubDownloaderV3) GetLabels() ([]*base.Label, error) {
  206. var perPage = g.maxPerPage
  207. var labels = make([]*base.Label, 0, perPage)
  208. for i := 1; ; i++ {
  209. g.sleep()
  210. ls, resp, err := g.client.Issues.ListLabels(g.ctx, g.repoOwner, g.repoName,
  211. &github.ListOptions{
  212. Page: i,
  213. PerPage: perPage,
  214. })
  215. if err != nil {
  216. return nil, err
  217. }
  218. g.rate = &resp.Rate
  219. for _, label := range ls {
  220. labels = append(labels, convertGithubLabel(label))
  221. }
  222. if len(ls) < perPage {
  223. break
  224. }
  225. }
  226. return labels, nil
  227. }
  228. func (g *GithubDownloaderV3) convertGithubRelease(rel *github.RepositoryRelease) *base.Release {
  229. r := &base.Release{
  230. Name: rel.GetName(),
  231. TagName: rel.GetTagName(),
  232. TargetCommitish: rel.GetTargetCommitish(),
  233. Draft: rel.GetDraft(),
  234. Prerelease: rel.GetPrerelease(),
  235. Created: rel.GetCreatedAt().Time,
  236. PublisherID: rel.GetAuthor().GetID(),
  237. PublisherName: rel.GetAuthor().GetLogin(),
  238. PublisherEmail: rel.GetAuthor().GetEmail(),
  239. Body: rel.GetBody(),
  240. }
  241. if rel.PublishedAt != nil {
  242. r.Published = rel.PublishedAt.Time
  243. }
  244. httpClient := &http.Client{
  245. Transport: &http.Transport{
  246. TLSClientConfig: &tls.Config{InsecureSkipVerify: setting.Migrations.SkipTLSVerify},
  247. Proxy: proxy.Proxy(),
  248. },
  249. }
  250. for _, asset := range rel.Assets {
  251. var assetID = *asset.ID // Don't optimize this, for closure we need a local variable
  252. r.Assets = append(r.Assets, &base.ReleaseAsset{
  253. ID: asset.GetID(),
  254. Name: asset.GetName(),
  255. ContentType: asset.ContentType,
  256. Size: asset.Size,
  257. DownloadCount: asset.DownloadCount,
  258. Created: asset.CreatedAt.Time,
  259. Updated: asset.UpdatedAt.Time,
  260. DownloadFunc: func() (io.ReadCloser, error) {
  261. g.sleep()
  262. asset, redirectURL, err := g.client.Repositories.DownloadReleaseAsset(g.ctx, g.repoOwner, g.repoName, assetID, nil)
  263. if err != nil {
  264. return nil, err
  265. }
  266. if err := g.RefreshRate(); err != nil {
  267. log.Error("g.client.RateLimits: %s", err)
  268. }
  269. if asset == nil {
  270. if redirectURL != "" {
  271. g.sleep()
  272. req, err := http.NewRequestWithContext(g.ctx, "GET", redirectURL, nil)
  273. if err != nil {
  274. return nil, err
  275. }
  276. resp, err := httpClient.Do(req)
  277. err1 := g.RefreshRate()
  278. if err1 != nil {
  279. log.Error("g.client.RateLimits: %s", err1)
  280. }
  281. if err != nil {
  282. return nil, err
  283. }
  284. return resp.Body, nil
  285. }
  286. return nil, fmt.Errorf("No release asset found for %d", assetID)
  287. }
  288. return asset, nil
  289. },
  290. })
  291. }
  292. return r
  293. }
  294. // GetReleases returns releases
  295. func (g *GithubDownloaderV3) GetReleases() ([]*base.Release, error) {
  296. var perPage = g.maxPerPage
  297. var releases = make([]*base.Release, 0, perPage)
  298. for i := 1; ; i++ {
  299. g.sleep()
  300. ls, resp, err := g.client.Repositories.ListReleases(g.ctx, g.repoOwner, g.repoName,
  301. &github.ListOptions{
  302. Page: i,
  303. PerPage: perPage,
  304. })
  305. if err != nil {
  306. return nil, err
  307. }
  308. g.rate = &resp.Rate
  309. for _, release := range ls {
  310. releases = append(releases, g.convertGithubRelease(release))
  311. }
  312. if len(ls) < perPage {
  313. break
  314. }
  315. }
  316. return releases, nil
  317. }
  318. // GetIssues returns issues according start and limit
  319. func (g *GithubDownloaderV3) GetIssues(page, perPage int) ([]*base.Issue, bool, error) {
  320. if perPage > g.maxPerPage {
  321. perPage = g.maxPerPage
  322. }
  323. opt := &github.IssueListByRepoOptions{
  324. Sort: "created",
  325. Direction: "asc",
  326. State: "all",
  327. ListOptions: github.ListOptions{
  328. PerPage: perPage,
  329. Page: page,
  330. },
  331. }
  332. var allIssues = make([]*base.Issue, 0, perPage)
  333. g.sleep()
  334. issues, resp, err := g.client.Issues.ListByRepo(g.ctx, g.repoOwner, g.repoName, opt)
  335. if err != nil {
  336. return nil, false, fmt.Errorf("error while listing repos: %v", err)
  337. }
  338. log.Trace("Request get issues %d/%d, but in fact get %d", perPage, page, len(issues))
  339. g.rate = &resp.Rate
  340. for _, issue := range issues {
  341. if issue.IsPullRequest() {
  342. continue
  343. }
  344. var labels = make([]*base.Label, 0, len(issue.Labels))
  345. for _, l := range issue.Labels {
  346. labels = append(labels, convertGithubLabel(l))
  347. }
  348. // get reactions
  349. var reactions []*base.Reaction
  350. for i := 1; ; i++ {
  351. g.sleep()
  352. res, resp, err := g.client.Reactions.ListIssueReactions(g.ctx, g.repoOwner, g.repoName, issue.GetNumber(), &github.ListOptions{
  353. Page: i,
  354. PerPage: perPage,
  355. })
  356. if err != nil {
  357. return nil, false, err
  358. }
  359. g.rate = &resp.Rate
  360. if len(res) == 0 {
  361. break
  362. }
  363. for _, reaction := range res {
  364. reactions = append(reactions, &base.Reaction{
  365. UserID: reaction.User.GetID(),
  366. UserName: reaction.User.GetLogin(),
  367. Content: reaction.GetContent(),
  368. })
  369. }
  370. }
  371. var assignees []string
  372. for i := range issue.Assignees {
  373. assignees = append(assignees, issue.Assignees[i].GetLogin())
  374. }
  375. allIssues = append(allIssues, &base.Issue{
  376. Title: *issue.Title,
  377. Number: int64(*issue.Number),
  378. PosterID: issue.GetUser().GetID(),
  379. PosterName: issue.GetUser().GetLogin(),
  380. PosterEmail: issue.GetUser().GetEmail(),
  381. Content: issue.GetBody(),
  382. Milestone: issue.GetMilestone().GetTitle(),
  383. State: issue.GetState(),
  384. Created: issue.GetCreatedAt(),
  385. Updated: issue.GetUpdatedAt(),
  386. Labels: labels,
  387. Reactions: reactions,
  388. Closed: issue.ClosedAt,
  389. IsLocked: issue.GetLocked(),
  390. Assignees: assignees,
  391. Context: base.BasicIssueContext(*issue.Number),
  392. })
  393. }
  394. return allIssues, len(issues) < perPage, nil
  395. }
  396. // SupportGetRepoComments return true if it supports get repo comments
  397. func (g *GithubDownloaderV3) SupportGetRepoComments() bool {
  398. return true
  399. }
  400. // GetComments returns comments according issueNumber
  401. func (g *GithubDownloaderV3) GetComments(opts base.GetCommentOptions) ([]*base.Comment, bool, error) {
  402. if opts.Context != nil {
  403. comments, err := g.getComments(opts.Context)
  404. return comments, false, err
  405. }
  406. return g.GetAllComments(opts.Page, opts.PageSize)
  407. }
  408. func (g *GithubDownloaderV3) getComments(issueContext base.IssueContext) ([]*base.Comment, error) {
  409. var (
  410. allComments = make([]*base.Comment, 0, g.maxPerPage)
  411. created = "created"
  412. asc = "asc"
  413. )
  414. opt := &github.IssueListCommentsOptions{
  415. Sort: &created,
  416. Direction: &asc,
  417. ListOptions: github.ListOptions{
  418. PerPage: g.maxPerPage,
  419. },
  420. }
  421. for {
  422. g.sleep()
  423. comments, resp, err := g.client.Issues.ListComments(g.ctx, g.repoOwner, g.repoName, int(issueContext.ForeignID()), opt)
  424. if err != nil {
  425. return nil, fmt.Errorf("error while listing repos: %v", err)
  426. }
  427. g.rate = &resp.Rate
  428. for _, comment := range comments {
  429. // get reactions
  430. var reactions []*base.Reaction
  431. for i := 1; ; i++ {
  432. g.sleep()
  433. res, resp, err := g.client.Reactions.ListIssueCommentReactions(g.ctx, g.repoOwner, g.repoName, comment.GetID(), &github.ListOptions{
  434. Page: i,
  435. PerPage: g.maxPerPage,
  436. })
  437. if err != nil {
  438. return nil, err
  439. }
  440. g.rate = &resp.Rate
  441. if len(res) == 0 {
  442. break
  443. }
  444. for _, reaction := range res {
  445. reactions = append(reactions, &base.Reaction{
  446. UserID: reaction.User.GetID(),
  447. UserName: reaction.User.GetLogin(),
  448. Content: reaction.GetContent(),
  449. })
  450. }
  451. }
  452. allComments = append(allComments, &base.Comment{
  453. IssueIndex: issueContext.LocalID(),
  454. PosterID: comment.GetUser().GetID(),
  455. PosterName: comment.GetUser().GetLogin(),
  456. PosterEmail: comment.GetUser().GetEmail(),
  457. Content: comment.GetBody(),
  458. Created: comment.GetCreatedAt(),
  459. Updated: comment.GetUpdatedAt(),
  460. Reactions: reactions,
  461. })
  462. }
  463. if resp.NextPage == 0 {
  464. break
  465. }
  466. opt.Page = resp.NextPage
  467. }
  468. return allComments, nil
  469. }
  470. // GetAllComments returns repository comments according page and perPageSize
  471. func (g *GithubDownloaderV3) GetAllComments(page, perPage int) ([]*base.Comment, bool, error) {
  472. var (
  473. allComments = make([]*base.Comment, 0, perPage)
  474. created = "created"
  475. asc = "asc"
  476. )
  477. opt := &github.IssueListCommentsOptions{
  478. Sort: &created,
  479. Direction: &asc,
  480. ListOptions: github.ListOptions{
  481. Page: page,
  482. PerPage: perPage,
  483. },
  484. }
  485. g.sleep()
  486. comments, resp, err := g.client.Issues.ListComments(g.ctx, g.repoOwner, g.repoName, 0, opt)
  487. if err != nil {
  488. return nil, false, fmt.Errorf("error while listing repos: %v", err)
  489. }
  490. log.Trace("Request get comments %d/%d, but in fact get %d", perPage, page, len(comments))
  491. g.rate = &resp.Rate
  492. for _, comment := range comments {
  493. // get reactions
  494. var reactions []*base.Reaction
  495. for i := 1; ; i++ {
  496. g.sleep()
  497. res, resp, err := g.client.Reactions.ListIssueCommentReactions(g.ctx, g.repoOwner, g.repoName, comment.GetID(), &github.ListOptions{
  498. Page: i,
  499. PerPage: g.maxPerPage,
  500. })
  501. if err != nil {
  502. return nil, false, err
  503. }
  504. g.rate = &resp.Rate
  505. if len(res) == 0 {
  506. break
  507. }
  508. for _, reaction := range res {
  509. reactions = append(reactions, &base.Reaction{
  510. UserID: reaction.User.GetID(),
  511. UserName: reaction.User.GetLogin(),
  512. Content: reaction.GetContent(),
  513. })
  514. }
  515. }
  516. idx := strings.LastIndex(*comment.IssueURL, "/")
  517. issueIndex, _ := strconv.ParseInt((*comment.IssueURL)[idx+1:], 10, 64)
  518. allComments = append(allComments, &base.Comment{
  519. IssueIndex: issueIndex,
  520. PosterID: comment.GetUser().GetID(),
  521. PosterName: comment.GetUser().GetLogin(),
  522. PosterEmail: comment.GetUser().GetEmail(),
  523. Content: comment.GetBody(),
  524. Created: comment.GetCreatedAt(),
  525. Updated: comment.GetUpdatedAt(),
  526. Reactions: reactions,
  527. })
  528. }
  529. return allComments, len(allComments) < perPage, nil
  530. }
  531. // GetPullRequests returns pull requests according page and perPage
  532. func (g *GithubDownloaderV3) GetPullRequests(page, perPage int) ([]*base.PullRequest, bool, error) {
  533. if perPage > g.maxPerPage {
  534. perPage = g.maxPerPage
  535. }
  536. opt := &github.PullRequestListOptions{
  537. Sort: "created",
  538. Direction: "asc",
  539. State: "all",
  540. ListOptions: github.ListOptions{
  541. PerPage: perPage,
  542. Page: page,
  543. },
  544. }
  545. var allPRs = make([]*base.PullRequest, 0, perPage)
  546. g.sleep()
  547. prs, resp, err := g.client.PullRequests.List(g.ctx, g.repoOwner, g.repoName, opt)
  548. if err != nil {
  549. return nil, false, fmt.Errorf("error while listing repos: %v", err)
  550. }
  551. log.Trace("Request get pull requests %d/%d, but in fact get %d", perPage, page, len(prs))
  552. g.rate = &resp.Rate
  553. for _, pr := range prs {
  554. var labels = make([]*base.Label, 0, len(pr.Labels))
  555. for _, l := range pr.Labels {
  556. labels = append(labels, convertGithubLabel(l))
  557. }
  558. // get reactions
  559. var reactions []*base.Reaction
  560. for i := 1; ; i++ {
  561. g.sleep()
  562. res, resp, err := g.client.Reactions.ListIssueReactions(g.ctx, g.repoOwner, g.repoName, pr.GetNumber(), &github.ListOptions{
  563. Page: i,
  564. PerPage: perPage,
  565. })
  566. if err != nil {
  567. return nil, false, err
  568. }
  569. g.rate = &resp.Rate
  570. if len(res) == 0 {
  571. break
  572. }
  573. for _, reaction := range res {
  574. reactions = append(reactions, &base.Reaction{
  575. UserID: reaction.User.GetID(),
  576. UserName: reaction.User.GetLogin(),
  577. Content: reaction.GetContent(),
  578. })
  579. }
  580. }
  581. allPRs = append(allPRs, &base.PullRequest{
  582. Title: pr.GetTitle(),
  583. Number: int64(pr.GetNumber()),
  584. PosterID: pr.GetUser().GetID(),
  585. PosterName: pr.GetUser().GetLogin(),
  586. PosterEmail: pr.GetUser().GetEmail(),
  587. Content: pr.GetBody(),
  588. Milestone: pr.GetMilestone().GetTitle(),
  589. State: pr.GetState(),
  590. Created: pr.GetCreatedAt(),
  591. Updated: pr.GetUpdatedAt(),
  592. Closed: pr.ClosedAt,
  593. Labels: labels,
  594. Merged: pr.MergedAt != nil,
  595. MergeCommitSHA: pr.GetMergeCommitSHA(),
  596. MergedTime: pr.MergedAt,
  597. IsLocked: pr.ActiveLockReason != nil,
  598. Head: base.PullRequestBranch{
  599. Ref: pr.GetHead().GetRef(),
  600. SHA: pr.GetHead().GetSHA(),
  601. OwnerName: pr.GetHead().GetUser().GetLogin(),
  602. RepoName: pr.GetHead().GetRepo().GetName(),
  603. CloneURL: pr.GetHead().GetRepo().GetCloneURL(),
  604. },
  605. Base: base.PullRequestBranch{
  606. Ref: pr.GetBase().GetRef(),
  607. SHA: pr.GetBase().GetSHA(),
  608. RepoName: pr.GetBase().GetRepo().GetName(),
  609. OwnerName: pr.GetBase().GetUser().GetLogin(),
  610. },
  611. PatchURL: pr.GetPatchURL(),
  612. Reactions: reactions,
  613. Context: base.BasicIssueContext(*pr.Number),
  614. })
  615. }
  616. return allPRs, len(prs) < perPage, nil
  617. }
  618. func convertGithubReview(r *github.PullRequestReview) *base.Review {
  619. return &base.Review{
  620. ID: r.GetID(),
  621. ReviewerID: r.GetUser().GetID(),
  622. ReviewerName: r.GetUser().GetLogin(),
  623. CommitID: r.GetCommitID(),
  624. Content: r.GetBody(),
  625. CreatedAt: r.GetSubmittedAt(),
  626. State: r.GetState(),
  627. }
  628. }
  629. func (g *GithubDownloaderV3) convertGithubReviewComments(cs []*github.PullRequestComment) ([]*base.ReviewComment, error) {
  630. var rcs = make([]*base.ReviewComment, 0, len(cs))
  631. for _, c := range cs {
  632. // get reactions
  633. var reactions []*base.Reaction
  634. for i := 1; ; i++ {
  635. g.sleep()
  636. res, resp, err := g.client.Reactions.ListPullRequestCommentReactions(g.ctx, g.repoOwner, g.repoName, c.GetID(), &github.ListOptions{
  637. Page: i,
  638. PerPage: g.maxPerPage,
  639. })
  640. if err != nil {
  641. return nil, err
  642. }
  643. g.rate = &resp.Rate
  644. if len(res) == 0 {
  645. break
  646. }
  647. for _, reaction := range res {
  648. reactions = append(reactions, &base.Reaction{
  649. UserID: reaction.User.GetID(),
  650. UserName: reaction.User.GetLogin(),
  651. Content: reaction.GetContent(),
  652. })
  653. }
  654. }
  655. rcs = append(rcs, &base.ReviewComment{
  656. ID: c.GetID(),
  657. InReplyTo: c.GetInReplyTo(),
  658. Content: c.GetBody(),
  659. TreePath: c.GetPath(),
  660. DiffHunk: c.GetDiffHunk(),
  661. Position: c.GetPosition(),
  662. CommitID: c.GetCommitID(),
  663. PosterID: c.GetUser().GetID(),
  664. Reactions: reactions,
  665. CreatedAt: c.GetCreatedAt(),
  666. UpdatedAt: c.GetUpdatedAt(),
  667. })
  668. }
  669. return rcs, nil
  670. }
  671. // GetReviews returns pull requests review
  672. func (g *GithubDownloaderV3) GetReviews(context base.IssueContext) ([]*base.Review, error) {
  673. var allReviews = make([]*base.Review, 0, g.maxPerPage)
  674. opt := &github.ListOptions{
  675. PerPage: g.maxPerPage,
  676. }
  677. for {
  678. g.sleep()
  679. reviews, resp, err := g.client.PullRequests.ListReviews(g.ctx, g.repoOwner, g.repoName, int(context.ForeignID()), opt)
  680. if err != nil {
  681. return nil, fmt.Errorf("error while listing repos: %v", err)
  682. }
  683. g.rate = &resp.Rate
  684. for _, review := range reviews {
  685. r := convertGithubReview(review)
  686. r.IssueIndex = context.LocalID()
  687. // retrieve all review comments
  688. opt2 := &github.ListOptions{
  689. PerPage: g.maxPerPage,
  690. }
  691. for {
  692. g.sleep()
  693. reviewComments, resp, err := g.client.PullRequests.ListReviewComments(g.ctx, g.repoOwner, g.repoName, int(context.ForeignID()), review.GetID(), opt2)
  694. if err != nil {
  695. return nil, fmt.Errorf("error while listing repos: %v", err)
  696. }
  697. g.rate = &resp.Rate
  698. cs, err := g.convertGithubReviewComments(reviewComments)
  699. if err != nil {
  700. return nil, err
  701. }
  702. r.Comments = append(r.Comments, cs...)
  703. if resp.NextPage == 0 {
  704. break
  705. }
  706. opt2.Page = resp.NextPage
  707. }
  708. allReviews = append(allReviews, r)
  709. }
  710. if resp.NextPage == 0 {
  711. break
  712. }
  713. opt.Page = resp.NextPage
  714. }
  715. return allReviews, nil
  716. }