gitea源码

gitlab.go 22KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777
  1. // Copyright 2019 The Gitea Authors. All rights reserved.
  2. // SPDX-License-Identifier: MIT
  3. package migrations
  4. import (
  5. "context"
  6. "errors"
  7. "fmt"
  8. "io"
  9. "net/http"
  10. "net/url"
  11. "path"
  12. "regexp"
  13. "strings"
  14. "time"
  15. issues_model "code.gitea.io/gitea/models/issues"
  16. "code.gitea.io/gitea/models/user"
  17. "code.gitea.io/gitea/modules/container"
  18. "code.gitea.io/gitea/modules/log"
  19. base "code.gitea.io/gitea/modules/migration"
  20. "code.gitea.io/gitea/modules/structs"
  21. gitlab "gitlab.com/gitlab-org/api/client-go"
  22. )
  23. var (
  24. _ base.Downloader = &GitlabDownloader{}
  25. _ base.DownloaderFactory = &GitlabDownloaderFactory{}
  26. )
  27. func init() {
  28. RegisterDownloaderFactory(&GitlabDownloaderFactory{})
  29. }
  30. // GitlabDownloaderFactory defines a gitlab downloader factory
  31. type GitlabDownloaderFactory struct{}
  32. // New returns a Downloader related to this factory according MigrateOptions
  33. func (f *GitlabDownloaderFactory) New(ctx context.Context, opts base.MigrateOptions) (base.Downloader, error) {
  34. u, err := url.Parse(opts.CloneAddr)
  35. if err != nil {
  36. return nil, err
  37. }
  38. baseURL := u.Scheme + "://" + u.Host
  39. repoNameSpace := strings.TrimPrefix(u.Path, "/")
  40. repoNameSpace = strings.TrimSuffix(repoNameSpace, ".git")
  41. log.Trace("Create gitlab downloader. BaseURL: %s RepoName: %s", baseURL, repoNameSpace)
  42. return NewGitlabDownloader(ctx, baseURL, repoNameSpace, opts.AuthToken)
  43. }
  44. // GitServiceType returns the type of git service
  45. func (f *GitlabDownloaderFactory) GitServiceType() structs.GitServiceType {
  46. return structs.GitlabService
  47. }
  48. type gitlabIIDResolver struct {
  49. maxIssueIID int64
  50. frozen bool
  51. }
  52. func (r *gitlabIIDResolver) recordIssueIID(issueIID int) {
  53. if r.frozen {
  54. panic("cannot record issue IID after pull request IID generation has started")
  55. }
  56. r.maxIssueIID = max(r.maxIssueIID, int64(issueIID))
  57. }
  58. func (r *gitlabIIDResolver) generatePullRequestNumber(mrIID int) int64 {
  59. r.frozen = true
  60. return r.maxIssueIID + int64(mrIID)
  61. }
  62. // GitlabDownloader implements a Downloader interface to get repository information
  63. // from gitlab via go-gitlab
  64. // - issueCount is incremented in GetIssues() to ensure PR and Issue numbers do not overlap,
  65. // because Gitlab has individual Issue and Pull Request numbers.
  66. type GitlabDownloader struct {
  67. base.NullDownloader
  68. client *gitlab.Client
  69. baseURL string
  70. repoID int
  71. repoName string
  72. iidResolver gitlabIIDResolver
  73. maxPerPage int
  74. }
  75. // NewGitlabDownloader creates a gitlab Downloader via gitlab API
  76. //
  77. // Use either a username/password, personal token entered into the username field, or anonymous/public access
  78. // Note: Public access only allows very basic access
  79. func NewGitlabDownloader(ctx context.Context, baseURL, repoPath, token string) (*GitlabDownloader, error) {
  80. gitlabClient, err := gitlab.NewClient(token, gitlab.WithBaseURL(baseURL), gitlab.WithHTTPClient(NewMigrationHTTPClient()))
  81. if err != nil {
  82. log.Trace("Error logging into gitlab: %v", err)
  83. return nil, err
  84. }
  85. // split namespace and subdirectory
  86. pathParts := strings.Split(strings.Trim(repoPath, "/"), "/")
  87. var resp *gitlab.Response
  88. u, _ := url.Parse(baseURL)
  89. for len(pathParts) >= 2 {
  90. _, resp, err = gitlabClient.Version.GetVersion()
  91. if err == nil || resp != nil && resp.StatusCode == http.StatusUnauthorized {
  92. err = nil // if no authentication given, this still should work
  93. break
  94. }
  95. u.Path = path.Join(u.Path, pathParts[0])
  96. baseURL = u.String()
  97. pathParts = pathParts[1:]
  98. _ = gitlab.WithBaseURL(baseURL)(gitlabClient)
  99. repoPath = strings.Join(pathParts, "/")
  100. }
  101. if err != nil {
  102. log.Trace("Error could not get gitlab version: %v", err)
  103. return nil, err
  104. }
  105. log.Trace("gitlab downloader: use BaseURL: '%s' and RepoPath: '%s'", baseURL, repoPath)
  106. // Grab and store project/repo ID here, due to issues using the URL escaped path
  107. gr, _, err := gitlabClient.Projects.GetProject(repoPath, nil, nil, gitlab.WithContext(ctx))
  108. if err != nil {
  109. log.Trace("Error retrieving project: %v", err)
  110. return nil, err
  111. }
  112. if gr == nil {
  113. log.Trace("Error getting project, project is nil")
  114. return nil, errors.New("Error getting project, project is nil")
  115. }
  116. return &GitlabDownloader{
  117. client: gitlabClient,
  118. baseURL: baseURL,
  119. repoID: gr.ID,
  120. repoName: gr.Name,
  121. maxPerPage: 100,
  122. }, nil
  123. }
  124. // String implements Stringer
  125. func (g *GitlabDownloader) String() string {
  126. return fmt.Sprintf("migration from gitlab server %s [%d]/%s", g.baseURL, g.repoID, g.repoName)
  127. }
  128. func (g *GitlabDownloader) LogString() string {
  129. if g == nil {
  130. return "<GitlabDownloader nil>"
  131. }
  132. return fmt.Sprintf("<GitlabDownloader %s [%d]/%s>", g.baseURL, g.repoID, g.repoName)
  133. }
  134. // GetRepoInfo returns a repository information
  135. func (g *GitlabDownloader) GetRepoInfo(ctx context.Context) (*base.Repository, error) {
  136. gr, _, err := g.client.Projects.GetProject(g.repoID, nil, nil, gitlab.WithContext(ctx))
  137. if err != nil {
  138. return nil, err
  139. }
  140. var private bool
  141. switch gr.Visibility {
  142. case gitlab.InternalVisibility:
  143. private = true
  144. case gitlab.PrivateVisibility:
  145. private = true
  146. }
  147. var owner string
  148. if gr.Owner == nil {
  149. log.Trace("gr.Owner is nil, trying to get owner from Namespace")
  150. if gr.Namespace != nil && gr.Namespace.Kind == "user" {
  151. owner = gr.Namespace.Path
  152. }
  153. } else {
  154. owner = gr.Owner.Username
  155. }
  156. // convert gitlab repo to stand Repo
  157. return &base.Repository{
  158. Owner: owner,
  159. Name: gr.Name,
  160. IsPrivate: private,
  161. Description: gr.Description,
  162. OriginalURL: gr.WebURL,
  163. CloneURL: gr.HTTPURLToRepo,
  164. DefaultBranch: gr.DefaultBranch,
  165. }, nil
  166. }
  167. // GetTopics return gitlab topics
  168. func (g *GitlabDownloader) GetTopics(ctx context.Context) ([]string, error) {
  169. gr, _, err := g.client.Projects.GetProject(g.repoID, nil, nil, gitlab.WithContext(ctx))
  170. if err != nil {
  171. return nil, err
  172. }
  173. return gr.Topics, err
  174. }
  175. // GetMilestones returns milestones
  176. func (g *GitlabDownloader) GetMilestones(ctx context.Context) ([]*base.Milestone, error) {
  177. perPage := g.maxPerPage
  178. state := "all"
  179. milestones := make([]*base.Milestone, 0, perPage)
  180. for i := 1; ; i++ {
  181. ms, _, err := g.client.Milestones.ListMilestones(g.repoID, &gitlab.ListMilestonesOptions{
  182. State: &state,
  183. ListOptions: gitlab.ListOptions{
  184. Page: i,
  185. PerPage: perPage,
  186. },
  187. }, nil, gitlab.WithContext(ctx))
  188. if err != nil {
  189. return nil, err
  190. }
  191. for _, m := range ms {
  192. var desc string
  193. if m.Description != "" {
  194. desc = m.Description
  195. }
  196. state := "open"
  197. var closedAt *time.Time
  198. if m.State != "" {
  199. state = m.State
  200. if state == "closed" {
  201. closedAt = m.UpdatedAt
  202. }
  203. }
  204. var deadline *time.Time
  205. if m.DueDate != nil {
  206. deadlineParsed, err := time.Parse("2006-01-02", m.DueDate.String())
  207. if err != nil {
  208. log.Trace("Error parsing Milestone DueDate time")
  209. deadline = nil
  210. } else {
  211. deadline = &deadlineParsed
  212. }
  213. }
  214. milestones = append(milestones, &base.Milestone{
  215. Title: m.Title,
  216. Description: desc,
  217. Deadline: deadline,
  218. State: state,
  219. Created: *m.CreatedAt,
  220. Updated: m.UpdatedAt,
  221. Closed: closedAt,
  222. })
  223. }
  224. if len(ms) < perPage {
  225. break
  226. }
  227. }
  228. return milestones, nil
  229. }
  230. func (g *GitlabDownloader) normalizeColor(val string) string {
  231. val = strings.TrimLeft(val, "#")
  232. val = strings.ToLower(val)
  233. if len(val) == 3 {
  234. c := []rune(val)
  235. val = fmt.Sprintf("%c%c%c%c%c%c", c[0], c[0], c[1], c[1], c[2], c[2])
  236. }
  237. if len(val) != 6 {
  238. return ""
  239. }
  240. return val
  241. }
  242. // GetLabels returns labels
  243. func (g *GitlabDownloader) GetLabels(ctx context.Context) ([]*base.Label, error) {
  244. perPage := g.maxPerPage
  245. labels := make([]*base.Label, 0, perPage)
  246. for i := 1; ; i++ {
  247. ls, _, err := g.client.Labels.ListLabels(g.repoID, &gitlab.ListLabelsOptions{ListOptions: gitlab.ListOptions{
  248. Page: i,
  249. PerPage: perPage,
  250. }}, nil, gitlab.WithContext(ctx))
  251. if err != nil {
  252. return nil, err
  253. }
  254. for _, label := range ls {
  255. baseLabel := &base.Label{
  256. Name: label.Name,
  257. Color: g.normalizeColor(label.Color),
  258. Description: label.Description,
  259. }
  260. labels = append(labels, baseLabel)
  261. }
  262. if len(ls) < perPage {
  263. break
  264. }
  265. }
  266. return labels, nil
  267. }
  268. func (g *GitlabDownloader) convertGitlabRelease(ctx context.Context, rel *gitlab.Release) *base.Release {
  269. var zero int
  270. r := &base.Release{
  271. TagName: rel.TagName,
  272. TargetCommitish: rel.Commit.ID,
  273. Name: rel.Name,
  274. Body: rel.Description,
  275. Created: *rel.CreatedAt,
  276. PublisherID: int64(rel.Author.ID),
  277. PublisherName: rel.Author.Username,
  278. }
  279. httpClient := NewMigrationHTTPClient()
  280. for k, asset := range rel.Assets.Links {
  281. assetID := asset.ID // Don't optimize this, for closure we need a local variable
  282. r.Assets = append(r.Assets, &base.ReleaseAsset{
  283. ID: int64(asset.ID),
  284. Name: asset.Name,
  285. ContentType: &rel.Assets.Sources[k].Format,
  286. Size: &zero,
  287. DownloadCount: &zero,
  288. DownloadFunc: func() (io.ReadCloser, error) {
  289. link, _, err := g.client.ReleaseLinks.GetReleaseLink(g.repoID, rel.TagName, assetID, gitlab.WithContext(ctx))
  290. if err != nil {
  291. return nil, err
  292. }
  293. if !hasBaseURL(link.URL, g.baseURL) {
  294. WarnAndNotice("Unexpected AssetURL for assetID[%d] in %s: %s", assetID, g, link.URL)
  295. return io.NopCloser(strings.NewReader(link.URL)), nil
  296. }
  297. req, err := http.NewRequest(http.MethodGet, link.URL, nil)
  298. if err != nil {
  299. return nil, err
  300. }
  301. req = req.WithContext(ctx)
  302. resp, err := httpClient.Do(req)
  303. if err != nil {
  304. return nil, err
  305. }
  306. // resp.Body is closed by the uploader
  307. return resp.Body, nil
  308. },
  309. })
  310. }
  311. return r
  312. }
  313. // GetReleases returns releases
  314. func (g *GitlabDownloader) GetReleases(ctx context.Context) ([]*base.Release, error) {
  315. perPage := g.maxPerPage
  316. releases := make([]*base.Release, 0, perPage)
  317. for i := 1; ; i++ {
  318. ls, _, err := g.client.Releases.ListReleases(g.repoID, &gitlab.ListReleasesOptions{
  319. ListOptions: gitlab.ListOptions{
  320. Page: i,
  321. PerPage: perPage,
  322. },
  323. }, nil, gitlab.WithContext(ctx))
  324. if err != nil {
  325. return nil, err
  326. }
  327. for _, release := range ls {
  328. releases = append(releases, g.convertGitlabRelease(ctx, release))
  329. }
  330. if len(ls) < perPage {
  331. break
  332. }
  333. }
  334. return releases, nil
  335. }
  336. type gitlabIssueContext struct {
  337. IsMergeRequest bool
  338. }
  339. // GetIssues returns issues according start and limit
  340. //
  341. // Note: issue label description and colors are not supported by the go-gitlab library at this time
  342. func (g *GitlabDownloader) GetIssues(ctx context.Context, page, perPage int) ([]*base.Issue, bool, error) {
  343. state := "all"
  344. sort := "asc"
  345. if perPage > g.maxPerPage {
  346. perPage = g.maxPerPage
  347. }
  348. opt := &gitlab.ListProjectIssuesOptions{
  349. State: &state,
  350. Sort: &sort,
  351. ListOptions: gitlab.ListOptions{
  352. PerPage: perPage,
  353. Page: page,
  354. },
  355. }
  356. allIssues := make([]*base.Issue, 0, perPage)
  357. issues, _, err := g.client.Issues.ListProjectIssues(g.repoID, opt, nil, gitlab.WithContext(ctx))
  358. if err != nil {
  359. return nil, false, fmt.Errorf("error while listing issues: %w", err)
  360. }
  361. for _, issue := range issues {
  362. labels := make([]*base.Label, 0, len(issue.Labels))
  363. for _, l := range issue.Labels {
  364. labels = append(labels, &base.Label{
  365. Name: l,
  366. })
  367. }
  368. var milestone string
  369. if issue.Milestone != nil {
  370. milestone = issue.Milestone.Title
  371. }
  372. var reactions []*gitlab.AwardEmoji
  373. awardPage := 1
  374. for {
  375. awards, _, err := g.client.AwardEmoji.ListIssueAwardEmoji(g.repoID, issue.IID, &gitlab.ListAwardEmojiOptions{Page: awardPage, PerPage: perPage}, gitlab.WithContext(ctx))
  376. if err != nil {
  377. return nil, false, fmt.Errorf("error while listing issue awards: %w", err)
  378. }
  379. reactions = append(reactions, awards...)
  380. if len(awards) < perPage {
  381. break
  382. }
  383. awardPage++
  384. }
  385. allIssues = append(allIssues, &base.Issue{
  386. Title: issue.Title,
  387. Number: int64(issue.IID),
  388. PosterID: int64(issue.Author.ID),
  389. PosterName: issue.Author.Username,
  390. Content: issue.Description,
  391. Milestone: milestone,
  392. State: issue.State,
  393. Created: *issue.CreatedAt,
  394. Labels: labels,
  395. Reactions: g.awardsToReactions(reactions),
  396. Closed: issue.ClosedAt,
  397. IsLocked: issue.DiscussionLocked,
  398. Updated: *issue.UpdatedAt,
  399. ForeignIndex: int64(issue.IID),
  400. Context: gitlabIssueContext{IsMergeRequest: false},
  401. })
  402. // record the issue IID, to be used in GetPullRequests()
  403. g.iidResolver.recordIssueIID(issue.IID)
  404. }
  405. return allIssues, len(issues) < perPage, nil
  406. }
  407. // GetComments returns comments according issueNumber
  408. // TODO: figure out how to transfer comment reactions
  409. func (g *GitlabDownloader) GetComments(ctx context.Context, commentable base.Commentable) ([]*base.Comment, bool, error) {
  410. context, ok := commentable.GetContext().(gitlabIssueContext)
  411. if !ok {
  412. return nil, false, fmt.Errorf("unexpected context: %+v", commentable.GetContext())
  413. }
  414. allComments := make([]*base.Comment, 0, g.maxPerPage)
  415. page := 1
  416. for {
  417. var comments []*gitlab.Discussion
  418. var resp *gitlab.Response
  419. var err error
  420. if !context.IsMergeRequest {
  421. comments, resp, err = g.client.Discussions.ListIssueDiscussions(g.repoID, int(commentable.GetForeignIndex()), &gitlab.ListIssueDiscussionsOptions{
  422. Page: page,
  423. PerPage: g.maxPerPage,
  424. }, nil, gitlab.WithContext(ctx))
  425. } else {
  426. comments, resp, err = g.client.Discussions.ListMergeRequestDiscussions(g.repoID, int(commentable.GetForeignIndex()), &gitlab.ListMergeRequestDiscussionsOptions{
  427. Page: page,
  428. PerPage: g.maxPerPage,
  429. }, nil, gitlab.WithContext(ctx))
  430. }
  431. if err != nil {
  432. return nil, false, fmt.Errorf("error while listing comments: %v %w", g.repoID, err)
  433. }
  434. for _, comment := range comments {
  435. for _, note := range comment.Notes {
  436. allComments = append(allComments, g.convertNoteToComment(commentable.GetLocalIndex(), note))
  437. }
  438. }
  439. if resp.NextPage == 0 {
  440. break
  441. }
  442. page = resp.NextPage
  443. }
  444. page = 1
  445. for {
  446. var stateEvents []*gitlab.StateEvent
  447. var resp *gitlab.Response
  448. var err error
  449. if context.IsMergeRequest {
  450. stateEvents, resp, err = g.client.ResourceStateEvents.ListMergeStateEvents(g.repoID, int(commentable.GetForeignIndex()), &gitlab.ListStateEventsOptions{
  451. ListOptions: gitlab.ListOptions{
  452. Page: page,
  453. PerPage: g.maxPerPage,
  454. },
  455. }, nil, gitlab.WithContext(ctx))
  456. } else {
  457. stateEvents, resp, err = g.client.ResourceStateEvents.ListIssueStateEvents(g.repoID, int(commentable.GetForeignIndex()), &gitlab.ListStateEventsOptions{
  458. ListOptions: gitlab.ListOptions{
  459. Page: page,
  460. PerPage: g.maxPerPage,
  461. },
  462. }, nil, gitlab.WithContext(ctx))
  463. }
  464. if err != nil {
  465. return nil, false, fmt.Errorf("error while listing state events: %v %w", g.repoID, err)
  466. }
  467. for _, stateEvent := range stateEvents {
  468. posterUserID, posterUsername := user.GhostUserID, user.GhostUserName
  469. if stateEvent.User != nil {
  470. posterUserID, posterUsername = int64(stateEvent.User.ID), stateEvent.User.Username
  471. }
  472. comment := &base.Comment{
  473. IssueIndex: commentable.GetLocalIndex(),
  474. Index: int64(stateEvent.ID),
  475. PosterID: posterUserID,
  476. PosterName: posterUsername,
  477. Content: "",
  478. Created: *stateEvent.CreatedAt,
  479. }
  480. switch stateEvent.State {
  481. case gitlab.ClosedEventType:
  482. comment.CommentType = issues_model.CommentTypeClose.String()
  483. case gitlab.MergedEventType:
  484. comment.CommentType = issues_model.CommentTypeMergePull.String()
  485. case gitlab.ReopenedEventType:
  486. comment.CommentType = issues_model.CommentTypeReopen.String()
  487. default:
  488. // Ignore other event types
  489. continue
  490. }
  491. allComments = append(allComments, comment)
  492. }
  493. if resp.NextPage == 0 {
  494. break
  495. }
  496. page = resp.NextPage
  497. }
  498. return allComments, true, nil
  499. }
  500. var targetBranchChangeRegexp = regexp.MustCompile("^changed target branch from `(.*?)` to `(.*?)`$")
  501. func (g *GitlabDownloader) convertNoteToComment(localIndex int64, note *gitlab.Note) *base.Comment {
  502. comment := &base.Comment{
  503. IssueIndex: localIndex,
  504. Index: int64(note.ID),
  505. PosterID: int64(note.Author.ID),
  506. PosterName: note.Author.Username,
  507. PosterEmail: note.Author.Email,
  508. Content: note.Body,
  509. Created: *note.CreatedAt,
  510. Meta: map[string]any{},
  511. }
  512. // Try to find the underlying event of system notes.
  513. if note.System {
  514. if match := targetBranchChangeRegexp.FindStringSubmatch(note.Body); match != nil {
  515. comment.CommentType = issues_model.CommentTypeChangeTargetBranch.String()
  516. comment.Meta["OldRef"] = match[1]
  517. comment.Meta["NewRef"] = match[2]
  518. } else if strings.HasPrefix(note.Body, "enabled an automatic merge") {
  519. comment.CommentType = issues_model.CommentTypePRScheduledToAutoMerge.String()
  520. } else if note.Body == "canceled the automatic merge" {
  521. comment.CommentType = issues_model.CommentTypePRUnScheduledToAutoMerge.String()
  522. }
  523. }
  524. return comment
  525. }
  526. // GetPullRequests returns pull requests according page and perPage
  527. func (g *GitlabDownloader) GetPullRequests(ctx context.Context, page, perPage int) ([]*base.PullRequest, bool, error) {
  528. if perPage > g.maxPerPage {
  529. perPage = g.maxPerPage
  530. }
  531. view := "simple"
  532. opt := &gitlab.ListProjectMergeRequestsOptions{
  533. ListOptions: gitlab.ListOptions{
  534. PerPage: perPage,
  535. Page: page,
  536. },
  537. View: &view,
  538. }
  539. allPRs := make([]*base.PullRequest, 0, perPage)
  540. prs, _, err := g.client.MergeRequests.ListProjectMergeRequests(g.repoID, opt, nil, gitlab.WithContext(ctx))
  541. if err != nil {
  542. return nil, false, fmt.Errorf("error while listing merge requests: %w", err)
  543. }
  544. for _, simplePR := range prs {
  545. // Load merge request again by itself, as not all fields are populated in the ListProjectMergeRequests endpoint.
  546. // See https://gitlab.com/gitlab-org/gitlab/-/issues/29620
  547. pr, _, err := g.client.MergeRequests.GetMergeRequest(g.repoID, simplePR.IID, nil)
  548. if err != nil {
  549. return nil, false, fmt.Errorf("error while loading merge request: %w", err)
  550. }
  551. labels := make([]*base.Label, 0, len(pr.Labels))
  552. for _, l := range pr.Labels {
  553. labels = append(labels, &base.Label{
  554. Name: l,
  555. })
  556. }
  557. var merged bool
  558. if pr.State == "merged" {
  559. merged = true
  560. pr.State = "closed"
  561. }
  562. mergeTime := pr.MergedAt
  563. if merged && pr.MergedAt == nil {
  564. mergeTime = pr.UpdatedAt
  565. }
  566. closeTime := pr.ClosedAt
  567. if merged && pr.ClosedAt == nil {
  568. closeTime = pr.UpdatedAt
  569. }
  570. mergeCommitSHA := pr.MergeCommitSHA
  571. if mergeCommitSHA == "" {
  572. mergeCommitSHA = pr.SquashCommitSHA
  573. }
  574. var locked bool
  575. if pr.State == "locked" {
  576. locked = true
  577. }
  578. var milestone string
  579. if pr.Milestone != nil {
  580. milestone = pr.Milestone.Title
  581. }
  582. var reactions []*gitlab.AwardEmoji
  583. awardPage := 1
  584. for {
  585. awards, _, err := g.client.AwardEmoji.ListMergeRequestAwardEmoji(g.repoID, pr.IID, &gitlab.ListAwardEmojiOptions{Page: awardPage, PerPage: perPage}, gitlab.WithContext(ctx))
  586. if err != nil {
  587. return nil, false, fmt.Errorf("error while listing merge requests awards: %w", err)
  588. }
  589. reactions = append(reactions, awards...)
  590. if len(awards) < perPage {
  591. break
  592. }
  593. awardPage++
  594. }
  595. // Generate new PR Numbers by the known Issue Numbers, because they share the same number space in Gitea, but they are independent in Gitlab
  596. newPRNumber := g.iidResolver.generatePullRequestNumber(pr.IID)
  597. allPRs = append(allPRs, &base.PullRequest{
  598. Title: pr.Title,
  599. Number: newPRNumber,
  600. PosterName: pr.Author.Username,
  601. PosterID: int64(pr.Author.ID),
  602. Content: pr.Description,
  603. Milestone: milestone,
  604. State: pr.State,
  605. Created: *pr.CreatedAt,
  606. Closed: closeTime,
  607. Labels: labels,
  608. Merged: merged,
  609. MergeCommitSHA: mergeCommitSHA,
  610. MergedTime: mergeTime,
  611. IsLocked: locked,
  612. Reactions: g.awardsToReactions(reactions),
  613. Head: base.PullRequestBranch{
  614. Ref: pr.SourceBranch,
  615. SHA: pr.SHA,
  616. RepoName: g.repoName,
  617. OwnerName: pr.Author.Username,
  618. CloneURL: pr.WebURL,
  619. },
  620. Base: base.PullRequestBranch{
  621. Ref: pr.TargetBranch,
  622. SHA: pr.DiffRefs.BaseSha,
  623. RepoName: g.repoName,
  624. OwnerName: pr.Author.Username,
  625. },
  626. PatchURL: pr.WebURL + ".patch",
  627. ForeignIndex: int64(pr.IID),
  628. Context: gitlabIssueContext{IsMergeRequest: true},
  629. IsDraft: pr.Draft,
  630. })
  631. // SECURITY: Ensure that the PR is safe
  632. _ = CheckAndEnsureSafePR(allPRs[len(allPRs)-1], g.baseURL, g)
  633. }
  634. return allPRs, len(prs) < perPage, nil
  635. }
  636. // GetReviews returns pull requests review
  637. func (g *GitlabDownloader) GetReviews(ctx context.Context, reviewable base.Reviewable) ([]*base.Review, error) {
  638. approvals, resp, err := g.client.MergeRequestApprovals.GetConfiguration(g.repoID, int(reviewable.GetForeignIndex()), gitlab.WithContext(ctx))
  639. if err != nil {
  640. if resp != nil && resp.StatusCode == http.StatusNotFound {
  641. log.Error(fmt.Sprintf("GitlabDownloader: while migrating a error occurred: '%s'", err.Error()))
  642. return []*base.Review{}, nil
  643. }
  644. return nil, err
  645. }
  646. var createdAt time.Time
  647. if approvals.CreatedAt != nil {
  648. createdAt = *approvals.CreatedAt
  649. } else if approvals.UpdatedAt != nil {
  650. createdAt = *approvals.UpdatedAt
  651. } else {
  652. createdAt = time.Now()
  653. }
  654. reviews := make([]*base.Review, 0, len(approvals.ApprovedBy))
  655. for _, user := range approvals.ApprovedBy {
  656. reviews = append(reviews, &base.Review{
  657. IssueIndex: reviewable.GetLocalIndex(),
  658. ReviewerID: int64(user.User.ID),
  659. ReviewerName: user.User.Username,
  660. CreatedAt: createdAt,
  661. // All we get are approvals
  662. State: base.ReviewStateApproved,
  663. })
  664. }
  665. return reviews, nil
  666. }
  667. func (g *GitlabDownloader) awardsToReactions(awards []*gitlab.AwardEmoji) []*base.Reaction {
  668. result := make([]*base.Reaction, 0, len(awards))
  669. uniqCheck := make(container.Set[string])
  670. for _, award := range awards {
  671. uid := fmt.Sprintf("%s%d", award.Name, award.User.ID)
  672. if uniqCheck.Add(uid) {
  673. result = append(result, &base.Reaction{
  674. UserID: int64(award.User.ID),
  675. UserName: award.User.Username,
  676. Content: award.Name,
  677. })
  678. }
  679. }
  680. return result
  681. }