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.

gitea_downloader.go 19KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686
  1. // Copyright 2020 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. "errors"
  8. "fmt"
  9. "io"
  10. "net/http"
  11. "net/url"
  12. "strings"
  13. "time"
  14. admin_model "code.gitea.io/gitea/models/admin"
  15. "code.gitea.io/gitea/modules/log"
  16. base "code.gitea.io/gitea/modules/migration"
  17. "code.gitea.io/gitea/modules/structs"
  18. gitea_sdk "code.gitea.io/sdk/gitea"
  19. )
  20. var (
  21. _ base.Downloader = &GiteaDownloader{}
  22. _ base.DownloaderFactory = &GiteaDownloaderFactory{}
  23. )
  24. func init() {
  25. RegisterDownloaderFactory(&GiteaDownloaderFactory{})
  26. }
  27. // GiteaDownloaderFactory defines a gitea downloader factory
  28. type GiteaDownloaderFactory struct {
  29. }
  30. // New returns a Downloader related to this factory according MigrateOptions
  31. func (f *GiteaDownloaderFactory) New(ctx context.Context, opts base.MigrateOptions) (base.Downloader, error) {
  32. u, err := url.Parse(opts.CloneAddr)
  33. if err != nil {
  34. return nil, err
  35. }
  36. baseURL := u.Scheme + "://" + u.Host
  37. repoNameSpace := strings.TrimPrefix(u.Path, "/")
  38. repoNameSpace = strings.TrimSuffix(repoNameSpace, ".git")
  39. path := strings.Split(repoNameSpace, "/")
  40. if len(path) < 2 {
  41. return nil, fmt.Errorf("invalid path: %s", repoNameSpace)
  42. }
  43. repoPath := strings.Join(path[len(path)-2:], "/")
  44. if len(path) > 2 {
  45. subPath := strings.Join(path[:len(path)-2], "/")
  46. baseURL += "/" + subPath
  47. }
  48. log.Trace("Create gitea downloader. BaseURL: %s RepoName: %s", baseURL, repoNameSpace)
  49. return NewGiteaDownloader(ctx, baseURL, repoPath, opts.AuthUsername, opts.AuthPassword, opts.AuthToken)
  50. }
  51. // GitServiceType returns the type of git service
  52. func (f *GiteaDownloaderFactory) GitServiceType() structs.GitServiceType {
  53. return structs.GiteaService
  54. }
  55. // GiteaDownloader implements a Downloader interface to get repository information's
  56. type GiteaDownloader struct {
  57. base.NullDownloader
  58. ctx context.Context
  59. client *gitea_sdk.Client
  60. repoOwner string
  61. repoName string
  62. pagination bool
  63. maxPerPage int
  64. }
  65. // NewGiteaDownloader creates a gitea Downloader via gitea API
  66. // Use either a username/password or personal token. token is preferred
  67. // Note: Public access only allows very basic access
  68. func NewGiteaDownloader(ctx context.Context, baseURL, repoPath, username, password, token string) (*GiteaDownloader, error) {
  69. giteaClient, err := gitea_sdk.NewClient(
  70. baseURL,
  71. gitea_sdk.SetToken(token),
  72. gitea_sdk.SetBasicAuth(username, password),
  73. gitea_sdk.SetContext(ctx),
  74. gitea_sdk.SetHTTPClient(NewMigrationHTTPClient()),
  75. )
  76. if err != nil {
  77. log.Error(fmt.Sprintf("Failed to create NewGiteaDownloader for: %s. Error: %v", baseURL, err))
  78. return nil, err
  79. }
  80. path := strings.Split(repoPath, "/")
  81. paginationSupport := true
  82. if err = giteaClient.CheckServerVersionConstraint(">=1.12"); err != nil {
  83. paginationSupport = false
  84. }
  85. // set small maxPerPage since we can only guess
  86. // (default would be 50 but this can differ)
  87. maxPerPage := 10
  88. // gitea instances >=1.13 can tell us what maximum they have
  89. apiConf, _, err := giteaClient.GetGlobalAPISettings()
  90. if err != nil {
  91. log.Info("Unable to get global API settings. Ignoring these.")
  92. log.Debug("giteaClient.GetGlobalAPISettings. Error: %v", err)
  93. }
  94. if apiConf != nil {
  95. maxPerPage = apiConf.MaxResponseItems
  96. }
  97. return &GiteaDownloader{
  98. ctx: ctx,
  99. client: giteaClient,
  100. repoOwner: path[0],
  101. repoName: path[1],
  102. pagination: paginationSupport,
  103. maxPerPage: maxPerPage,
  104. }, nil
  105. }
  106. // SetContext set context
  107. func (g *GiteaDownloader) SetContext(ctx context.Context) {
  108. g.ctx = ctx
  109. }
  110. // GetRepoInfo returns a repository information
  111. func (g *GiteaDownloader) GetRepoInfo() (*base.Repository, error) {
  112. if g == nil {
  113. return nil, errors.New("error: GiteaDownloader is nil")
  114. }
  115. repo, _, err := g.client.GetRepo(g.repoOwner, g.repoName)
  116. if err != nil {
  117. return nil, err
  118. }
  119. return &base.Repository{
  120. Name: repo.Name,
  121. Owner: repo.Owner.UserName,
  122. IsPrivate: repo.Private,
  123. Description: repo.Description,
  124. CloneURL: repo.CloneURL,
  125. OriginalURL: repo.HTMLURL,
  126. DefaultBranch: repo.DefaultBranch,
  127. }, nil
  128. }
  129. // GetTopics return gitea topics
  130. func (g *GiteaDownloader) GetTopics() ([]string, error) {
  131. topics, _, err := g.client.ListRepoTopics(g.repoOwner, g.repoName, gitea_sdk.ListRepoTopicsOptions{})
  132. return topics, err
  133. }
  134. // GetMilestones returns milestones
  135. func (g *GiteaDownloader) GetMilestones() ([]*base.Milestone, error) {
  136. var milestones = make([]*base.Milestone, 0, g.maxPerPage)
  137. for i := 1; ; i++ {
  138. // make sure gitea can shutdown gracefully
  139. select {
  140. case <-g.ctx.Done():
  141. return nil, nil
  142. default:
  143. }
  144. ms, _, err := g.client.ListRepoMilestones(g.repoOwner, g.repoName, gitea_sdk.ListMilestoneOption{
  145. ListOptions: gitea_sdk.ListOptions{
  146. PageSize: g.maxPerPage,
  147. Page: i,
  148. },
  149. State: gitea_sdk.StateAll,
  150. })
  151. if err != nil {
  152. return nil, err
  153. }
  154. for i := range ms {
  155. // old gitea instances dont have this information
  156. createdAT := time.Time{}
  157. var updatedAT *time.Time
  158. if ms[i].Closed != nil {
  159. createdAT = *ms[i].Closed
  160. updatedAT = ms[i].Closed
  161. }
  162. // new gitea instances (>=1.13) do
  163. if !ms[i].Created.IsZero() {
  164. createdAT = ms[i].Created
  165. }
  166. if ms[i].Updated != nil && !ms[i].Updated.IsZero() {
  167. updatedAT = ms[i].Updated
  168. }
  169. milestones = append(milestones, &base.Milestone{
  170. Title: ms[i].Title,
  171. Description: ms[i].Description,
  172. Deadline: ms[i].Deadline,
  173. Created: createdAT,
  174. Updated: updatedAT,
  175. Closed: ms[i].Closed,
  176. State: string(ms[i].State),
  177. })
  178. }
  179. if !g.pagination || len(ms) < g.maxPerPage {
  180. break
  181. }
  182. }
  183. return milestones, nil
  184. }
  185. func (g *GiteaDownloader) convertGiteaLabel(label *gitea_sdk.Label) *base.Label {
  186. return &base.Label{
  187. Name: label.Name,
  188. Color: label.Color,
  189. Description: label.Description,
  190. }
  191. }
  192. // GetLabels returns labels
  193. func (g *GiteaDownloader) GetLabels() ([]*base.Label, error) {
  194. var labels = make([]*base.Label, 0, g.maxPerPage)
  195. for i := 1; ; i++ {
  196. // make sure gitea can shutdown gracefully
  197. select {
  198. case <-g.ctx.Done():
  199. return nil, nil
  200. default:
  201. }
  202. ls, _, err := g.client.ListRepoLabels(g.repoOwner, g.repoName, gitea_sdk.ListLabelsOptions{ListOptions: gitea_sdk.ListOptions{
  203. PageSize: g.maxPerPage,
  204. Page: i,
  205. }})
  206. if err != nil {
  207. return nil, err
  208. }
  209. for i := range ls {
  210. labels = append(labels, g.convertGiteaLabel(ls[i]))
  211. }
  212. if !g.pagination || len(ls) < g.maxPerPage {
  213. break
  214. }
  215. }
  216. return labels, nil
  217. }
  218. func (g *GiteaDownloader) convertGiteaRelease(rel *gitea_sdk.Release) *base.Release {
  219. r := &base.Release{
  220. TagName: rel.TagName,
  221. TargetCommitish: rel.Target,
  222. Name: rel.Title,
  223. Body: rel.Note,
  224. Draft: rel.IsDraft,
  225. Prerelease: rel.IsPrerelease,
  226. PublisherID: rel.Publisher.ID,
  227. PublisherName: rel.Publisher.UserName,
  228. PublisherEmail: rel.Publisher.Email,
  229. Published: rel.PublishedAt,
  230. Created: rel.CreatedAt,
  231. }
  232. httpClient := NewMigrationHTTPClient()
  233. for _, asset := range rel.Attachments {
  234. size := int(asset.Size)
  235. dlCount := int(asset.DownloadCount)
  236. r.Assets = append(r.Assets, &base.ReleaseAsset{
  237. ID: asset.ID,
  238. Name: asset.Name,
  239. Size: &size,
  240. DownloadCount: &dlCount,
  241. Created: asset.Created,
  242. DownloadURL: &asset.DownloadURL,
  243. DownloadFunc: func() (io.ReadCloser, error) {
  244. asset, _, err := g.client.GetReleaseAttachment(g.repoOwner, g.repoName, rel.ID, asset.ID)
  245. if err != nil {
  246. return nil, err
  247. }
  248. // FIXME: for a private download?
  249. req, err := http.NewRequest("GET", asset.DownloadURL, nil)
  250. if err != nil {
  251. return nil, err
  252. }
  253. resp, err := httpClient.Do(req)
  254. if err != nil {
  255. return nil, err
  256. }
  257. // resp.Body is closed by the uploader
  258. return resp.Body, nil
  259. },
  260. })
  261. }
  262. return r
  263. }
  264. // GetReleases returns releases
  265. func (g *GiteaDownloader) GetReleases() ([]*base.Release, error) {
  266. var releases = make([]*base.Release, 0, g.maxPerPage)
  267. for i := 1; ; i++ {
  268. // make sure gitea can shutdown gracefully
  269. select {
  270. case <-g.ctx.Done():
  271. return nil, nil
  272. default:
  273. }
  274. rl, _, err := g.client.ListReleases(g.repoOwner, g.repoName, gitea_sdk.ListReleasesOptions{ListOptions: gitea_sdk.ListOptions{
  275. PageSize: g.maxPerPage,
  276. Page: i,
  277. }})
  278. if err != nil {
  279. return nil, err
  280. }
  281. for i := range rl {
  282. releases = append(releases, g.convertGiteaRelease(rl[i]))
  283. }
  284. if !g.pagination || len(rl) < g.maxPerPage {
  285. break
  286. }
  287. }
  288. return releases, nil
  289. }
  290. func (g *GiteaDownloader) getIssueReactions(index int64) ([]*base.Reaction, error) {
  291. var reactions []*base.Reaction
  292. if err := g.client.CheckServerVersionConstraint(">=1.11"); err != nil {
  293. log.Info("GiteaDownloader: instance to old, skip getIssueReactions")
  294. return reactions, nil
  295. }
  296. rl, _, err := g.client.GetIssueReactions(g.repoOwner, g.repoName, index)
  297. if err != nil {
  298. return nil, err
  299. }
  300. for _, reaction := range rl {
  301. reactions = append(reactions, &base.Reaction{
  302. UserID: reaction.User.ID,
  303. UserName: reaction.User.UserName,
  304. Content: reaction.Reaction,
  305. })
  306. }
  307. return reactions, nil
  308. }
  309. func (g *GiteaDownloader) getCommentReactions(commentID int64) ([]*base.Reaction, error) {
  310. var reactions []*base.Reaction
  311. if err := g.client.CheckServerVersionConstraint(">=1.11"); err != nil {
  312. log.Info("GiteaDownloader: instance to old, skip getCommentReactions")
  313. return reactions, nil
  314. }
  315. rl, _, err := g.client.GetIssueCommentReactions(g.repoOwner, g.repoName, commentID)
  316. if err != nil {
  317. return nil, err
  318. }
  319. for i := range rl {
  320. reactions = append(reactions, &base.Reaction{
  321. UserID: rl[i].User.ID,
  322. UserName: rl[i].User.UserName,
  323. Content: rl[i].Reaction,
  324. })
  325. }
  326. return reactions, nil
  327. }
  328. // GetIssues returns issues according start and limit
  329. func (g *GiteaDownloader) GetIssues(page, perPage int) ([]*base.Issue, bool, error) {
  330. if perPage > g.maxPerPage {
  331. perPage = g.maxPerPage
  332. }
  333. var allIssues = make([]*base.Issue, 0, perPage)
  334. issues, _, err := g.client.ListRepoIssues(g.repoOwner, g.repoName, gitea_sdk.ListIssueOption{
  335. ListOptions: gitea_sdk.ListOptions{Page: page, PageSize: perPage},
  336. State: gitea_sdk.StateAll,
  337. Type: gitea_sdk.IssueTypeIssue,
  338. })
  339. if err != nil {
  340. return nil, false, fmt.Errorf("error while listing issues: %v", err)
  341. }
  342. for _, issue := range issues {
  343. var labels = make([]*base.Label, 0, len(issue.Labels))
  344. for i := range issue.Labels {
  345. labels = append(labels, g.convertGiteaLabel(issue.Labels[i]))
  346. }
  347. var milestone string
  348. if issue.Milestone != nil {
  349. milestone = issue.Milestone.Title
  350. }
  351. reactions, err := g.getIssueReactions(issue.Index)
  352. if err != nil {
  353. log.Warn("Unable to load reactions during migrating issue #%d to %s/%s. Error: %v", issue.Index, g.repoOwner, g.repoName, err)
  354. if err2 := admin_model.CreateRepositoryNotice(
  355. fmt.Sprintf("Unable to load reactions during migrating issue #%d to %s/%s. Error: %v", issue.Index, g.repoOwner, g.repoName, err)); err2 != nil {
  356. log.Error("create repository notice failed: ", err2)
  357. }
  358. }
  359. var assignees []string
  360. for i := range issue.Assignees {
  361. assignees = append(assignees, issue.Assignees[i].UserName)
  362. }
  363. allIssues = append(allIssues, &base.Issue{
  364. Title: issue.Title,
  365. Number: issue.Index,
  366. PosterID: issue.Poster.ID,
  367. PosterName: issue.Poster.UserName,
  368. PosterEmail: issue.Poster.Email,
  369. Content: issue.Body,
  370. Milestone: milestone,
  371. State: string(issue.State),
  372. Created: issue.Created,
  373. Updated: issue.Updated,
  374. Closed: issue.Closed,
  375. Reactions: reactions,
  376. Labels: labels,
  377. Assignees: assignees,
  378. IsLocked: issue.IsLocked,
  379. Context: base.BasicIssueContext(issue.Index),
  380. })
  381. }
  382. isEnd := len(issues) < perPage
  383. if !g.pagination {
  384. isEnd = len(issues) == 0
  385. }
  386. return allIssues, isEnd, nil
  387. }
  388. // GetComments returns comments according issueNumber
  389. func (g *GiteaDownloader) GetComments(opts base.GetCommentOptions) ([]*base.Comment, bool, error) {
  390. var allComments = make([]*base.Comment, 0, g.maxPerPage)
  391. for i := 1; ; i++ {
  392. // make sure gitea can shutdown gracefully
  393. select {
  394. case <-g.ctx.Done():
  395. return nil, false, nil
  396. default:
  397. }
  398. comments, _, err := g.client.ListIssueComments(g.repoOwner, g.repoName, opts.Context.ForeignID(), gitea_sdk.ListIssueCommentOptions{ListOptions: gitea_sdk.ListOptions{
  399. PageSize: g.maxPerPage,
  400. Page: i,
  401. }})
  402. if err != nil {
  403. return nil, false, fmt.Errorf("error while listing comments for issue #%d. Error: %v", opts.Context.ForeignID(), err)
  404. }
  405. for _, comment := range comments {
  406. reactions, err := g.getCommentReactions(comment.ID)
  407. if err != nil {
  408. log.Warn("Unable to load comment reactions during migrating issue #%d for comment %d to %s/%s. Error: %v", opts.Context.ForeignID(), comment.ID, g.repoOwner, g.repoName, err)
  409. if err2 := admin_model.CreateRepositoryNotice(
  410. fmt.Sprintf("Unable to load reactions during migrating issue #%d for comment %d to %s/%s. Error: %v", opts.Context.ForeignID(), comment.ID, g.repoOwner, g.repoName, err)); err2 != nil {
  411. log.Error("create repository notice failed: ", err2)
  412. }
  413. }
  414. allComments = append(allComments, &base.Comment{
  415. IssueIndex: opts.Context.LocalID(),
  416. PosterID: comment.Poster.ID,
  417. PosterName: comment.Poster.UserName,
  418. PosterEmail: comment.Poster.Email,
  419. Content: comment.Body,
  420. Created: comment.Created,
  421. Updated: comment.Updated,
  422. Reactions: reactions,
  423. })
  424. }
  425. if !g.pagination || len(comments) < g.maxPerPage {
  426. break
  427. }
  428. }
  429. return allComments, true, nil
  430. }
  431. // GetPullRequests returns pull requests according page and perPage
  432. func (g *GiteaDownloader) GetPullRequests(page, perPage int) ([]*base.PullRequest, bool, error) {
  433. if perPage > g.maxPerPage {
  434. perPage = g.maxPerPage
  435. }
  436. var allPRs = make([]*base.PullRequest, 0, perPage)
  437. prs, _, err := g.client.ListRepoPullRequests(g.repoOwner, g.repoName, gitea_sdk.ListPullRequestsOptions{
  438. ListOptions: gitea_sdk.ListOptions{
  439. Page: page,
  440. PageSize: perPage,
  441. },
  442. State: gitea_sdk.StateAll,
  443. })
  444. if err != nil {
  445. return nil, false, fmt.Errorf("error while listing pull requests (page: %d, pagesize: %d). Error: %v", page, perPage, err)
  446. }
  447. for _, pr := range prs {
  448. var milestone string
  449. if pr.Milestone != nil {
  450. milestone = pr.Milestone.Title
  451. }
  452. var labels = make([]*base.Label, 0, len(pr.Labels))
  453. for i := range pr.Labels {
  454. labels = append(labels, g.convertGiteaLabel(pr.Labels[i]))
  455. }
  456. var (
  457. headUserName string
  458. headRepoName string
  459. headCloneURL string
  460. headRef string
  461. headSHA string
  462. )
  463. if pr.Head != nil {
  464. if pr.Head.Repository != nil {
  465. headUserName = pr.Head.Repository.Owner.UserName
  466. headRepoName = pr.Head.Repository.Name
  467. headCloneURL = pr.Head.Repository.CloneURL
  468. }
  469. headSHA = pr.Head.Sha
  470. headRef = pr.Head.Ref
  471. }
  472. var mergeCommitSHA string
  473. if pr.MergedCommitID != nil {
  474. mergeCommitSHA = *pr.MergedCommitID
  475. }
  476. reactions, err := g.getIssueReactions(pr.Index)
  477. if err != nil {
  478. log.Warn("Unable to load reactions during migrating pull #%d to %s/%s. Error: %v", pr.Index, g.repoOwner, g.repoName, err)
  479. if err2 := admin_model.CreateRepositoryNotice(
  480. fmt.Sprintf("Unable to load reactions during migrating pull #%d to %s/%s. Error: %v", pr.Index, g.repoOwner, g.repoName, err)); err2 != nil {
  481. log.Error("create repository notice failed: ", err2)
  482. }
  483. }
  484. var assignees []string
  485. for i := range pr.Assignees {
  486. assignees = append(assignees, pr.Assignees[i].UserName)
  487. }
  488. createdAt := time.Time{}
  489. if pr.Created != nil {
  490. createdAt = *pr.Created
  491. }
  492. updatedAt := time.Time{}
  493. if pr.Created != nil {
  494. updatedAt = *pr.Updated
  495. }
  496. closedAt := pr.Closed
  497. if pr.Merged != nil && closedAt == nil {
  498. closedAt = pr.Merged
  499. }
  500. allPRs = append(allPRs, &base.PullRequest{
  501. Title: pr.Title,
  502. Number: pr.Index,
  503. PosterID: pr.Poster.ID,
  504. PosterName: pr.Poster.UserName,
  505. PosterEmail: pr.Poster.Email,
  506. Content: pr.Body,
  507. State: string(pr.State),
  508. Created: createdAt,
  509. Updated: updatedAt,
  510. Closed: closedAt,
  511. Labels: labels,
  512. Milestone: milestone,
  513. Reactions: reactions,
  514. Assignees: assignees,
  515. Merged: pr.HasMerged,
  516. MergedTime: pr.Merged,
  517. MergeCommitSHA: mergeCommitSHA,
  518. IsLocked: pr.IsLocked,
  519. PatchURL: pr.PatchURL,
  520. Head: base.PullRequestBranch{
  521. Ref: headRef,
  522. SHA: headSHA,
  523. RepoName: headRepoName,
  524. OwnerName: headUserName,
  525. CloneURL: headCloneURL,
  526. },
  527. Base: base.PullRequestBranch{
  528. Ref: pr.Base.Ref,
  529. SHA: pr.Base.Sha,
  530. RepoName: g.repoName,
  531. OwnerName: g.repoOwner,
  532. },
  533. Context: base.BasicIssueContext(pr.Index),
  534. })
  535. }
  536. isEnd := len(prs) < perPage
  537. if !g.pagination {
  538. isEnd = len(prs) == 0
  539. }
  540. return allPRs, isEnd, nil
  541. }
  542. // GetReviews returns pull requests review
  543. func (g *GiteaDownloader) GetReviews(context base.IssueContext) ([]*base.Review, error) {
  544. if err := g.client.CheckServerVersionConstraint(">=1.12"); err != nil {
  545. log.Info("GiteaDownloader: instance to old, skip GetReviews")
  546. return nil, nil
  547. }
  548. var allReviews = make([]*base.Review, 0, g.maxPerPage)
  549. for i := 1; ; i++ {
  550. // make sure gitea can shutdown gracefully
  551. select {
  552. case <-g.ctx.Done():
  553. return nil, nil
  554. default:
  555. }
  556. prl, _, err := g.client.ListPullReviews(g.repoOwner, g.repoName, context.ForeignID(), gitea_sdk.ListPullReviewsOptions{ListOptions: gitea_sdk.ListOptions{
  557. Page: i,
  558. PageSize: g.maxPerPage,
  559. }})
  560. if err != nil {
  561. return nil, err
  562. }
  563. for _, pr := range prl {
  564. rcl, _, err := g.client.ListPullReviewComments(g.repoOwner, g.repoName, context.ForeignID(), pr.ID)
  565. if err != nil {
  566. return nil, err
  567. }
  568. var reviewComments []*base.ReviewComment
  569. for i := range rcl {
  570. line := int(rcl[i].LineNum)
  571. if rcl[i].OldLineNum > 0 {
  572. line = int(rcl[i].OldLineNum) * -1
  573. }
  574. reviewComments = append(reviewComments, &base.ReviewComment{
  575. ID: rcl[i].ID,
  576. Content: rcl[i].Body,
  577. TreePath: rcl[i].Path,
  578. DiffHunk: rcl[i].DiffHunk,
  579. Line: line,
  580. CommitID: rcl[i].CommitID,
  581. PosterID: rcl[i].Reviewer.ID,
  582. CreatedAt: rcl[i].Created,
  583. UpdatedAt: rcl[i].Updated,
  584. })
  585. }
  586. allReviews = append(allReviews, &base.Review{
  587. ID: pr.ID,
  588. IssueIndex: context.LocalID(),
  589. ReviewerID: pr.Reviewer.ID,
  590. ReviewerName: pr.Reviewer.UserName,
  591. Official: pr.Official,
  592. CommitID: pr.CommitID,
  593. Content: pr.Body,
  594. CreatedAt: pr.Submitted,
  595. State: string(pr.State),
  596. Comments: reviewComments,
  597. })
  598. }
  599. if len(prl) < g.maxPerPage {
  600. break
  601. }
  602. }
  603. return allReviews, nil
  604. }