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

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