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_uploader.go 23KB

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