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 18KB

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