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.go 22KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845
  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. "bytes"
  8. "context"
  9. "fmt"
  10. "io"
  11. "net/http"
  12. "net/url"
  13. "os"
  14. "path"
  15. "path/filepath"
  16. "strings"
  17. "sync"
  18. "time"
  19. "code.gitea.io/gitea/models"
  20. "code.gitea.io/gitea/modules/git"
  21. "code.gitea.io/gitea/modules/log"
  22. "code.gitea.io/gitea/modules/migrations/base"
  23. "code.gitea.io/gitea/modules/repository"
  24. repo_module "code.gitea.io/gitea/modules/repository"
  25. "code.gitea.io/gitea/modules/setting"
  26. "code.gitea.io/gitea/modules/structs"
  27. "code.gitea.io/gitea/modules/timeutil"
  28. gouuid "github.com/google/uuid"
  29. )
  30. var (
  31. _ base.Uploader = &GiteaLocalUploader{}
  32. )
  33. // GiteaLocalUploader implements an Uploader to gitea sites
  34. type GiteaLocalUploader struct {
  35. ctx context.Context
  36. doer *models.User
  37. repoOwner string
  38. repoName string
  39. repo *models.Repository
  40. labels sync.Map
  41. milestones sync.Map
  42. issues sync.Map
  43. gitRepo *git.Repository
  44. prHeadCache map[string]struct{}
  45. userMap map[int64]int64 // external user id mapping to user id
  46. prCache map[int64]*models.PullRequest
  47. gitServiceType structs.GitServiceType
  48. }
  49. // NewGiteaLocalUploader creates an gitea Uploader via gitea API v1
  50. func NewGiteaLocalUploader(ctx context.Context, doer *models.User, repoOwner, repoName string) *GiteaLocalUploader {
  51. return &GiteaLocalUploader{
  52. ctx: ctx,
  53. doer: doer,
  54. repoOwner: repoOwner,
  55. repoName: repoName,
  56. prHeadCache: make(map[string]struct{}),
  57. userMap: make(map[int64]int64),
  58. prCache: make(map[int64]*models.PullRequest),
  59. }
  60. }
  61. // MaxBatchInsertSize returns the table's max batch insert size
  62. func (g *GiteaLocalUploader) MaxBatchInsertSize(tp string) int {
  63. switch tp {
  64. case "issue":
  65. return models.MaxBatchInsertSize(new(models.Issue))
  66. case "comment":
  67. return models.MaxBatchInsertSize(new(models.Comment))
  68. case "milestone":
  69. return models.MaxBatchInsertSize(new(models.Milestone))
  70. case "label":
  71. return models.MaxBatchInsertSize(new(models.Label))
  72. case "release":
  73. return models.MaxBatchInsertSize(new(models.Release))
  74. case "pullrequest":
  75. return models.MaxBatchInsertSize(new(models.PullRequest))
  76. }
  77. return 10
  78. }
  79. // CreateRepo creates a repository
  80. func (g *GiteaLocalUploader) CreateRepo(repo *base.Repository, opts base.MigrateOptions) error {
  81. owner, err := models.GetUserByName(g.repoOwner)
  82. if err != nil {
  83. return err
  84. }
  85. var remoteAddr = repo.CloneURL
  86. if len(opts.AuthUsername) > 0 {
  87. u, err := url.Parse(repo.CloneURL)
  88. if err != nil {
  89. return err
  90. }
  91. u.User = url.UserPassword(opts.AuthUsername, opts.AuthPassword)
  92. remoteAddr = u.String()
  93. }
  94. var r *models.Repository
  95. if opts.MigrateToRepoID <= 0 {
  96. r, err = repo_module.CreateRepository(g.doer, owner, models.CreateRepoOptions{
  97. Name: g.repoName,
  98. Description: repo.Description,
  99. OriginalURL: repo.OriginalURL,
  100. GitServiceType: opts.GitServiceType,
  101. IsPrivate: opts.Private,
  102. IsMirror: opts.Mirror,
  103. Status: models.RepositoryBeingMigrated,
  104. })
  105. } else {
  106. r, err = models.GetRepositoryByID(opts.MigrateToRepoID)
  107. }
  108. if err != nil {
  109. return err
  110. }
  111. r, err = repository.MigrateRepositoryGitData(g.doer, owner, r, structs.MigrateRepoOption{
  112. RepoName: g.repoName,
  113. Description: repo.Description,
  114. OriginalURL: repo.OriginalURL,
  115. GitServiceType: opts.GitServiceType,
  116. Mirror: repo.IsMirror,
  117. CloneAddr: remoteAddr,
  118. Private: repo.IsPrivate,
  119. Wiki: opts.Wiki,
  120. Releases: opts.Releases, // if didn't get releases, then sync them from tags
  121. })
  122. g.repo = r
  123. if err != nil {
  124. return err
  125. }
  126. g.gitRepo, err = git.OpenRepository(r.RepoPath())
  127. return err
  128. }
  129. // Close closes this uploader
  130. func (g *GiteaLocalUploader) Close() {
  131. if g.gitRepo != nil {
  132. g.gitRepo.Close()
  133. }
  134. }
  135. // CreateTopics creates topics
  136. func (g *GiteaLocalUploader) CreateTopics(topics ...string) error {
  137. return models.SaveTopics(g.repo.ID, topics...)
  138. }
  139. // CreateMilestones creates milestones
  140. func (g *GiteaLocalUploader) CreateMilestones(milestones ...*base.Milestone) error {
  141. var mss = make([]*models.Milestone, 0, len(milestones))
  142. for _, milestone := range milestones {
  143. var deadline timeutil.TimeStamp
  144. if milestone.Deadline != nil {
  145. deadline = timeutil.TimeStamp(milestone.Deadline.Unix())
  146. }
  147. if deadline == 0 {
  148. deadline = timeutil.TimeStamp(time.Date(9999, 1, 1, 0, 0, 0, 0, setting.DefaultUILocation).Unix())
  149. }
  150. var ms = models.Milestone{
  151. RepoID: g.repo.ID,
  152. Name: milestone.Title,
  153. Content: milestone.Description,
  154. IsClosed: milestone.State == "closed",
  155. DeadlineUnix: deadline,
  156. }
  157. if ms.IsClosed && milestone.Closed != nil {
  158. ms.ClosedDateUnix = timeutil.TimeStamp(milestone.Closed.Unix())
  159. }
  160. mss = append(mss, &ms)
  161. }
  162. err := models.InsertMilestones(mss...)
  163. if err != nil {
  164. return err
  165. }
  166. for _, ms := range mss {
  167. g.milestones.Store(ms.Name, ms.ID)
  168. }
  169. return nil
  170. }
  171. // CreateLabels creates labels
  172. func (g *GiteaLocalUploader) CreateLabels(labels ...*base.Label) error {
  173. var lbs = make([]*models.Label, 0, len(labels))
  174. for _, label := range labels {
  175. lbs = append(lbs, &models.Label{
  176. RepoID: g.repo.ID,
  177. Name: label.Name,
  178. Description: label.Description,
  179. Color: fmt.Sprintf("#%s", label.Color),
  180. })
  181. }
  182. err := models.NewLabels(lbs...)
  183. if err != nil {
  184. return err
  185. }
  186. for _, lb := range lbs {
  187. g.labels.Store(lb.Name, lb)
  188. }
  189. return nil
  190. }
  191. // CreateReleases creates releases
  192. func (g *GiteaLocalUploader) CreateReleases(releases ...*base.Release) error {
  193. var rels = make([]*models.Release, 0, len(releases))
  194. for _, release := range releases {
  195. var rel = models.Release{
  196. RepoID: g.repo.ID,
  197. TagName: release.TagName,
  198. LowerTagName: strings.ToLower(release.TagName),
  199. Target: release.TargetCommitish,
  200. Title: release.Name,
  201. Sha1: release.TargetCommitish,
  202. Note: release.Body,
  203. IsDraft: release.Draft,
  204. IsPrerelease: release.Prerelease,
  205. IsTag: false,
  206. CreatedUnix: timeutil.TimeStamp(release.Created.Unix()),
  207. }
  208. userid, ok := g.userMap[release.PublisherID]
  209. tp := g.gitServiceType.Name()
  210. if !ok && tp != "" {
  211. var err error
  212. userid, err = models.GetUserIDByExternalUserID(tp, fmt.Sprintf("%v", release.PublisherID))
  213. if err != nil {
  214. log.Error("GetUserIDByExternalUserID: %v", err)
  215. }
  216. if userid > 0 {
  217. g.userMap[release.PublisherID] = userid
  218. }
  219. }
  220. if userid > 0 {
  221. rel.PublisherID = userid
  222. } else {
  223. rel.PublisherID = g.doer.ID
  224. rel.OriginalAuthor = release.PublisherName
  225. rel.OriginalAuthorID = release.PublisherID
  226. }
  227. // calc NumCommits
  228. commit, err := g.gitRepo.GetCommit(rel.TagName)
  229. if err != nil {
  230. return fmt.Errorf("GetCommit: %v", err)
  231. }
  232. rel.NumCommits, err = commit.CommitsCount()
  233. if err != nil {
  234. return fmt.Errorf("CommitsCount: %v", err)
  235. }
  236. for _, asset := range release.Assets {
  237. var attach = models.Attachment{
  238. UUID: gouuid.New().String(),
  239. Name: asset.Name,
  240. DownloadCount: int64(*asset.DownloadCount),
  241. Size: int64(*asset.Size),
  242. CreatedUnix: timeutil.TimeStamp(asset.Created.Unix()),
  243. }
  244. // download attachment
  245. err = func() error {
  246. resp, err := http.Get(asset.URL)
  247. if err != nil {
  248. return err
  249. }
  250. defer resp.Body.Close()
  251. localPath := attach.LocalPath()
  252. if err = os.MkdirAll(path.Dir(localPath), os.ModePerm); err != nil {
  253. return fmt.Errorf("MkdirAll: %v", err)
  254. }
  255. fw, err := os.Create(localPath)
  256. if err != nil {
  257. return fmt.Errorf("Create: %v", err)
  258. }
  259. defer fw.Close()
  260. _, err = io.Copy(fw, resp.Body)
  261. return err
  262. }()
  263. if err != nil {
  264. return err
  265. }
  266. rel.Attachments = append(rel.Attachments, &attach)
  267. }
  268. rels = append(rels, &rel)
  269. }
  270. return models.InsertReleases(rels...)
  271. }
  272. // SyncTags syncs releases with tags in the database
  273. func (g *GiteaLocalUploader) SyncTags() error {
  274. return repository.SyncReleasesWithTags(g.repo, g.gitRepo)
  275. }
  276. // CreateIssues creates issues
  277. func (g *GiteaLocalUploader) CreateIssues(issues ...*base.Issue) error {
  278. var iss = make([]*models.Issue, 0, len(issues))
  279. for _, issue := range issues {
  280. var labels []*models.Label
  281. for _, label := range issue.Labels {
  282. lb, ok := g.labels.Load(label.Name)
  283. if ok {
  284. labels = append(labels, lb.(*models.Label))
  285. }
  286. }
  287. var milestoneID int64
  288. if issue.Milestone != "" {
  289. milestone, ok := g.milestones.Load(issue.Milestone)
  290. if ok {
  291. milestoneID = milestone.(int64)
  292. }
  293. }
  294. var is = models.Issue{
  295. RepoID: g.repo.ID,
  296. Repo: g.repo,
  297. Index: issue.Number,
  298. Title: issue.Title,
  299. Content: issue.Content,
  300. IsClosed: issue.State == "closed",
  301. IsLocked: issue.IsLocked,
  302. MilestoneID: milestoneID,
  303. Labels: labels,
  304. CreatedUnix: timeutil.TimeStamp(issue.Created.Unix()),
  305. UpdatedUnix: timeutil.TimeStamp(issue.Updated.Unix()),
  306. }
  307. userid, ok := g.userMap[issue.PosterID]
  308. tp := g.gitServiceType.Name()
  309. if !ok && tp != "" {
  310. var err error
  311. userid, err = models.GetUserIDByExternalUserID(tp, fmt.Sprintf("%v", issue.PosterID))
  312. if err != nil {
  313. log.Error("GetUserIDByExternalUserID: %v", err)
  314. }
  315. if userid > 0 {
  316. g.userMap[issue.PosterID] = userid
  317. }
  318. }
  319. if userid > 0 {
  320. is.PosterID = userid
  321. } else {
  322. is.PosterID = g.doer.ID
  323. is.OriginalAuthor = issue.PosterName
  324. is.OriginalAuthorID = issue.PosterID
  325. }
  326. if issue.Closed != nil {
  327. is.ClosedUnix = timeutil.TimeStamp(issue.Closed.Unix())
  328. }
  329. // add reactions
  330. for _, reaction := range issue.Reactions {
  331. userid, ok := g.userMap[reaction.UserID]
  332. if !ok && tp != "" {
  333. var err error
  334. userid, err = models.GetUserIDByExternalUserID(tp, fmt.Sprintf("%v", reaction.UserID))
  335. if err != nil {
  336. log.Error("GetUserIDByExternalUserID: %v", err)
  337. }
  338. if userid > 0 {
  339. g.userMap[reaction.UserID] = userid
  340. }
  341. }
  342. var res = models.Reaction{
  343. Type: reaction.Content,
  344. CreatedUnix: timeutil.TimeStampNow(),
  345. }
  346. if userid > 0 {
  347. res.UserID = userid
  348. } else {
  349. res.UserID = g.doer.ID
  350. res.OriginalAuthorID = reaction.UserID
  351. res.OriginalAuthor = reaction.UserName
  352. }
  353. is.Reactions = append(is.Reactions, &res)
  354. }
  355. iss = append(iss, &is)
  356. }
  357. if len(iss) > 0 {
  358. if err := models.InsertIssues(iss...); err != nil {
  359. return err
  360. }
  361. for _, is := range iss {
  362. g.issues.Store(is.Index, is.ID)
  363. }
  364. }
  365. return nil
  366. }
  367. // CreateComments creates comments of issues
  368. func (g *GiteaLocalUploader) CreateComments(comments ...*base.Comment) error {
  369. var cms = make([]*models.Comment, 0, len(comments))
  370. for _, comment := range comments {
  371. var issueID int64
  372. if issueIDStr, ok := g.issues.Load(comment.IssueIndex); !ok {
  373. issue, err := models.GetIssueByIndex(g.repo.ID, comment.IssueIndex)
  374. if err != nil {
  375. return err
  376. }
  377. issueID = issue.ID
  378. g.issues.Store(comment.IssueIndex, issueID)
  379. } else {
  380. issueID = issueIDStr.(int64)
  381. }
  382. userid, ok := g.userMap[comment.PosterID]
  383. tp := g.gitServiceType.Name()
  384. if !ok && tp != "" {
  385. var err error
  386. userid, err = models.GetUserIDByExternalUserID(tp, fmt.Sprintf("%v", comment.PosterID))
  387. if err != nil {
  388. log.Error("GetUserIDByExternalUserID: %v", err)
  389. }
  390. if userid > 0 {
  391. g.userMap[comment.PosterID] = userid
  392. }
  393. }
  394. cm := models.Comment{
  395. IssueID: issueID,
  396. Type: models.CommentTypeComment,
  397. Content: comment.Content,
  398. CreatedUnix: timeutil.TimeStamp(comment.Created.Unix()),
  399. UpdatedUnix: timeutil.TimeStamp(comment.Updated.Unix()),
  400. }
  401. if userid > 0 {
  402. cm.PosterID = userid
  403. } else {
  404. cm.PosterID = g.doer.ID
  405. cm.OriginalAuthor = comment.PosterName
  406. cm.OriginalAuthorID = comment.PosterID
  407. }
  408. // add reactions
  409. for _, reaction := range comment.Reactions {
  410. userid, ok := g.userMap[reaction.UserID]
  411. if !ok && tp != "" {
  412. var err error
  413. userid, err = models.GetUserIDByExternalUserID(tp, fmt.Sprintf("%v", reaction.UserID))
  414. if err != nil {
  415. log.Error("GetUserIDByExternalUserID: %v", err)
  416. }
  417. if userid > 0 {
  418. g.userMap[reaction.UserID] = userid
  419. }
  420. }
  421. var res = models.Reaction{
  422. Type: reaction.Content,
  423. CreatedUnix: timeutil.TimeStampNow(),
  424. }
  425. if userid > 0 {
  426. res.UserID = userid
  427. } else {
  428. res.UserID = g.doer.ID
  429. res.OriginalAuthorID = reaction.UserID
  430. res.OriginalAuthor = reaction.UserName
  431. }
  432. cm.Reactions = append(cm.Reactions, &res)
  433. }
  434. cms = append(cms, &cm)
  435. }
  436. if len(cms) == 0 {
  437. return nil
  438. }
  439. return models.InsertIssueComments(cms)
  440. }
  441. // CreatePullRequests creates pull requests
  442. func (g *GiteaLocalUploader) CreatePullRequests(prs ...*base.PullRequest) error {
  443. var gprs = make([]*models.PullRequest, 0, len(prs))
  444. for _, pr := range prs {
  445. gpr, err := g.newPullRequest(pr)
  446. if err != nil {
  447. return err
  448. }
  449. userid, ok := g.userMap[pr.PosterID]
  450. tp := g.gitServiceType.Name()
  451. if !ok && tp != "" {
  452. var err error
  453. userid, err = models.GetUserIDByExternalUserID(tp, fmt.Sprintf("%v", pr.PosterID))
  454. if err != nil {
  455. log.Error("GetUserIDByExternalUserID: %v", err)
  456. }
  457. if userid > 0 {
  458. g.userMap[pr.PosterID] = userid
  459. }
  460. }
  461. if userid > 0 {
  462. gpr.Issue.PosterID = userid
  463. } else {
  464. gpr.Issue.PosterID = g.doer.ID
  465. gpr.Issue.OriginalAuthor = pr.PosterName
  466. gpr.Issue.OriginalAuthorID = pr.PosterID
  467. }
  468. gprs = append(gprs, gpr)
  469. }
  470. if err := models.InsertPullRequests(gprs...); err != nil {
  471. return err
  472. }
  473. for _, pr := range gprs {
  474. g.issues.Store(pr.Issue.Index, pr.Issue.ID)
  475. }
  476. return nil
  477. }
  478. func (g *GiteaLocalUploader) newPullRequest(pr *base.PullRequest) (*models.PullRequest, error) {
  479. var labels []*models.Label
  480. for _, label := range pr.Labels {
  481. lb, ok := g.labels.Load(label.Name)
  482. if ok {
  483. labels = append(labels, lb.(*models.Label))
  484. }
  485. }
  486. var milestoneID int64
  487. if pr.Milestone != "" {
  488. milestone, ok := g.milestones.Load(pr.Milestone)
  489. if ok {
  490. milestoneID = milestone.(int64)
  491. }
  492. }
  493. // download patch file
  494. err := func() error {
  495. resp, err := http.Get(pr.PatchURL)
  496. if err != nil {
  497. return err
  498. }
  499. defer resp.Body.Close()
  500. pullDir := filepath.Join(g.repo.RepoPath(), "pulls")
  501. if err = os.MkdirAll(pullDir, os.ModePerm); err != nil {
  502. return err
  503. }
  504. f, err := os.Create(filepath.Join(pullDir, fmt.Sprintf("%d.patch", pr.Number)))
  505. if err != nil {
  506. return err
  507. }
  508. defer f.Close()
  509. _, err = io.Copy(f, resp.Body)
  510. return err
  511. }()
  512. if err != nil {
  513. return nil, err
  514. }
  515. // set head information
  516. pullHead := filepath.Join(g.repo.RepoPath(), "refs", "pull", fmt.Sprintf("%d", pr.Number))
  517. if err := os.MkdirAll(pullHead, os.ModePerm); err != nil {
  518. return nil, err
  519. }
  520. p, err := os.Create(filepath.Join(pullHead, "head"))
  521. if err != nil {
  522. return nil, err
  523. }
  524. _, err = p.WriteString(pr.Head.SHA)
  525. p.Close()
  526. if err != nil {
  527. return nil, err
  528. }
  529. var head = "unknown repository"
  530. if pr.IsForkPullRequest() && pr.State != "closed" {
  531. if pr.Head.OwnerName != "" {
  532. remote := pr.Head.OwnerName
  533. _, ok := g.prHeadCache[remote]
  534. if !ok {
  535. // git remote add
  536. err := g.gitRepo.AddRemote(remote, pr.Head.CloneURL, true)
  537. if err != nil {
  538. log.Error("AddRemote failed: %s", err)
  539. } else {
  540. g.prHeadCache[remote] = struct{}{}
  541. ok = true
  542. }
  543. }
  544. if ok {
  545. _, err = git.NewCommand("fetch", remote, pr.Head.Ref).RunInDir(g.repo.RepoPath())
  546. if err != nil {
  547. log.Error("Fetch branch from %s failed: %v", pr.Head.CloneURL, err)
  548. } else {
  549. headBranch := filepath.Join(g.repo.RepoPath(), "refs", "heads", pr.Head.OwnerName, pr.Head.Ref)
  550. if err := os.MkdirAll(filepath.Dir(headBranch), os.ModePerm); err != nil {
  551. return nil, err
  552. }
  553. b, err := os.Create(headBranch)
  554. if err != nil {
  555. return nil, err
  556. }
  557. _, err = b.WriteString(pr.Head.SHA)
  558. b.Close()
  559. if err != nil {
  560. return nil, err
  561. }
  562. head = pr.Head.OwnerName + "/" + pr.Head.Ref
  563. }
  564. }
  565. }
  566. } else {
  567. head = pr.Head.Ref
  568. }
  569. var issue = models.Issue{
  570. RepoID: g.repo.ID,
  571. Repo: g.repo,
  572. Title: pr.Title,
  573. Index: pr.Number,
  574. Content: pr.Content,
  575. MilestoneID: milestoneID,
  576. IsPull: true,
  577. IsClosed: pr.State == "closed",
  578. IsLocked: pr.IsLocked,
  579. Labels: labels,
  580. CreatedUnix: timeutil.TimeStamp(pr.Created.Unix()),
  581. UpdatedUnix: timeutil.TimeStamp(pr.Updated.Unix()),
  582. }
  583. tp := g.gitServiceType.Name()
  584. userid, ok := g.userMap[pr.PosterID]
  585. if !ok && tp != "" {
  586. var err error
  587. userid, err = models.GetUserIDByExternalUserID(tp, fmt.Sprintf("%v", pr.PosterID))
  588. if err != nil {
  589. log.Error("GetUserIDByExternalUserID: %v", err)
  590. }
  591. if userid > 0 {
  592. g.userMap[pr.PosterID] = userid
  593. }
  594. }
  595. if userid > 0 {
  596. issue.PosterID = userid
  597. } else {
  598. issue.PosterID = g.doer.ID
  599. issue.OriginalAuthor = pr.PosterName
  600. issue.OriginalAuthorID = pr.PosterID
  601. }
  602. // add reactions
  603. for _, reaction := range pr.Reactions {
  604. userid, ok := g.userMap[reaction.UserID]
  605. if !ok && tp != "" {
  606. var err error
  607. userid, err = models.GetUserIDByExternalUserID(tp, fmt.Sprintf("%v", reaction.UserID))
  608. if err != nil {
  609. log.Error("GetUserIDByExternalUserID: %v", err)
  610. }
  611. if userid > 0 {
  612. g.userMap[reaction.UserID] = userid
  613. }
  614. }
  615. var res = models.Reaction{
  616. Type: reaction.Content,
  617. CreatedUnix: timeutil.TimeStampNow(),
  618. }
  619. if userid > 0 {
  620. res.UserID = userid
  621. } else {
  622. res.UserID = g.doer.ID
  623. res.OriginalAuthorID = reaction.UserID
  624. res.OriginalAuthor = reaction.UserName
  625. }
  626. issue.Reactions = append(issue.Reactions, &res)
  627. }
  628. var pullRequest = models.PullRequest{
  629. HeadRepoID: g.repo.ID,
  630. HeadBranch: head,
  631. BaseRepoID: g.repo.ID,
  632. BaseBranch: pr.Base.Ref,
  633. MergeBase: pr.Base.SHA,
  634. Index: pr.Number,
  635. HasMerged: pr.Merged,
  636. Issue: &issue,
  637. }
  638. if pullRequest.Issue.IsClosed && pr.Closed != nil {
  639. pullRequest.Issue.ClosedUnix = timeutil.TimeStamp(pr.Closed.Unix())
  640. }
  641. if pullRequest.HasMerged && pr.MergedTime != nil {
  642. pullRequest.MergedUnix = timeutil.TimeStamp(pr.MergedTime.Unix())
  643. pullRequest.MergedCommitID = pr.MergeCommitSHA
  644. pullRequest.MergerID = g.doer.ID
  645. }
  646. // TODO: assignees
  647. return &pullRequest, nil
  648. }
  649. func convertReviewState(state string) models.ReviewType {
  650. switch state {
  651. case base.ReviewStatePending:
  652. return models.ReviewTypePending
  653. case base.ReviewStateApproved:
  654. return models.ReviewTypeApprove
  655. case base.ReviewStateChangesRequested:
  656. return models.ReviewTypeReject
  657. case base.ReviewStateCommented:
  658. return models.ReviewTypeComment
  659. default:
  660. return models.ReviewTypePending
  661. }
  662. }
  663. // CreateReviews create pull request reviews
  664. func (g *GiteaLocalUploader) CreateReviews(reviews ...*base.Review) error {
  665. var cms = make([]*models.Review, 0, len(reviews))
  666. for _, review := range reviews {
  667. var issueID int64
  668. if issueIDStr, ok := g.issues.Load(review.IssueIndex); !ok {
  669. issue, err := models.GetIssueByIndex(g.repo.ID, review.IssueIndex)
  670. if err != nil {
  671. return err
  672. }
  673. issueID = issue.ID
  674. g.issues.Store(review.IssueIndex, issueID)
  675. } else {
  676. issueID = issueIDStr.(int64)
  677. }
  678. userid, ok := g.userMap[review.ReviewerID]
  679. tp := g.gitServiceType.Name()
  680. if !ok && tp != "" {
  681. var err error
  682. userid, err = models.GetUserIDByExternalUserID(tp, fmt.Sprintf("%v", review.ReviewerID))
  683. if err != nil {
  684. log.Error("GetUserIDByExternalUserID: %v", err)
  685. }
  686. if userid > 0 {
  687. g.userMap[review.ReviewerID] = userid
  688. }
  689. }
  690. var cm = models.Review{
  691. Type: convertReviewState(review.State),
  692. IssueID: issueID,
  693. Content: review.Content,
  694. Official: review.Official,
  695. CreatedUnix: timeutil.TimeStamp(review.CreatedAt.Unix()),
  696. UpdatedUnix: timeutil.TimeStamp(review.CreatedAt.Unix()),
  697. }
  698. if userid > 0 {
  699. cm.ReviewerID = userid
  700. } else {
  701. cm.ReviewerID = g.doer.ID
  702. cm.OriginalAuthor = review.ReviewerName
  703. cm.OriginalAuthorID = review.ReviewerID
  704. }
  705. // get pr
  706. pr, ok := g.prCache[issueID]
  707. if !ok {
  708. var err error
  709. pr, err = models.GetPullRequestByIssueIDWithNoAttributes(issueID)
  710. if err != nil {
  711. return err
  712. }
  713. g.prCache[issueID] = pr
  714. }
  715. for _, comment := range review.Comments {
  716. _, _, line, _ := git.ParseDiffHunkString(comment.DiffHunk)
  717. headCommitID, err := g.gitRepo.GetRefCommitID(pr.GetGitRefName())
  718. if err != nil {
  719. return fmt.Errorf("GetRefCommitID[%s]: %v", pr.GetGitRefName(), err)
  720. }
  721. var patch string
  722. patchBuf := new(bytes.Buffer)
  723. if err := git.GetRepoRawDiffForFile(g.gitRepo, pr.MergeBase, headCommitID, git.RawDiffNormal, comment.TreePath, patchBuf); err != nil {
  724. // We should ignore the error since the commit maybe removed when force push to the pull request
  725. log.Warn("GetRepoRawDiffForFile failed when migrating [%s, %s, %s, %s]: %v", g.gitRepo.Path, pr.MergeBase, headCommitID, comment.TreePath, err)
  726. } else {
  727. patch = git.CutDiffAroundLine(patchBuf, int64((&models.Comment{Line: int64(line + comment.Position - 1)}).UnsignedLine()), line < 0, setting.UI.CodeCommentLines)
  728. }
  729. var c = models.Comment{
  730. Type: models.CommentTypeCode,
  731. PosterID: comment.PosterID,
  732. IssueID: issueID,
  733. Content: comment.Content,
  734. Line: int64(line + comment.Position - 1),
  735. TreePath: comment.TreePath,
  736. CommitSHA: comment.CommitID,
  737. Patch: patch,
  738. CreatedUnix: timeutil.TimeStamp(comment.CreatedAt.Unix()),
  739. UpdatedUnix: timeutil.TimeStamp(comment.UpdatedAt.Unix()),
  740. }
  741. if userid > 0 {
  742. c.PosterID = userid
  743. } else {
  744. c.PosterID = g.doer.ID
  745. c.OriginalAuthor = review.ReviewerName
  746. c.OriginalAuthorID = review.ReviewerID
  747. }
  748. cm.Comments = append(cm.Comments, &c)
  749. }
  750. cms = append(cms, &cm)
  751. }
  752. return models.InsertReviews(cms)
  753. }
  754. // Rollback when migrating failed, this will rollback all the changes.
  755. func (g *GiteaLocalUploader) Rollback() error {
  756. if g.repo != nil && g.repo.ID > 0 {
  757. if err := models.DeleteRepository(g.doer, g.repo.OwnerID, g.repo.ID); err != nil {
  758. return err
  759. }
  760. }
  761. return nil
  762. }