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

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